简体   繁体   English

std :: string类成员应该是指针吗?

[英]Should a std::string class member be a pointer?

And why/why not? 为什么/为什么不呢?

Say I have a class which takes a string in the constructor and stores it. 假设我有一个类,它在构造函数中接受一个字符串并存储它。 Should this class member be a pointer, or just a value? 这个类成员应该是一个指针,还是一个值?

class X {
    X(const std::string& s): s(s) {}
    const std::string s;
};

Or... 要么...

class X {
    X(const std::string* s): s(s) {}
    const std::string* s;
};

If I was storing a primitive type, I'd take a copy. 如果我存储原始类型,我会复制一份。 If I was storing an object, I'd use a pointer. 如果我正在存储一个对象,我会使用一个指针。

I feel like I want to copy that string, but I don't know when to decide that. 我觉得我复制那个字符串,但我不知道何时决定。 Should I copy vectors? 我应该复制载体吗? Sets? 集? Maps? 地图? Entire JSON files...? 整个JSON文件......?

EDIT: 编辑:

Sounds like I need to read up on move semantics. 听起来我需要阅读移动语义。 But regardless, I'd like to make my question a little more specific: 但无论如何,我想让我的问题更具体一些:

If I have a 10 megabyte file as a const string, I really don't want to copy that. 如果我有一个10兆字节的文件作为常量字符串,我真的不想复制它。

If I'm newing up 100 objects, passing a 5 character const string into each one's constructor, none of them ought to have ownership. 如果我正在新建100个对象,将5个字符的const字符串传递给每个构造函数,那么它们都不应该拥有所有权。 Probably just take a copy of the string. 可能只是拿一个字符串的副本。

So (assuming I'm not completely wrong) it's obvious what to do from outside the class, but when you're designing class GenericTextHaver , how do you decide the method of text-having? 所以(假设我并非完全错误)很明显该从课外做什么,但是当你设计class GenericTextHaver ,你如何决定文本的方法呢?

If all you need is a class that takes a const string in its constructor, and allows you to get a const string with the same value out of it, how do you decide how to represent it internally? 如果你需要的是一个类,需要一个常量字符串在其构造,并让你获得一个常量字符串具有相同值出来的,你怎么决定如何在内部表示呢?

Should a std::string class member be a pointer? std :: string类成员应该是指针吗?

No 没有

And why not? 那么为何不?

Because std::string, like every other object in the standard library, and every other well-written object in c++ is designed to be treated as a value. 因为std :: string与标准库中的每个其他对象一样,并且c ++中的每个其他编写良好的对象都被设计为被视为值。

It may or may not use pointers internally - that is not your concern. 它可能会也可能不会在内部使用指针 - 这不是您的关注点。 All you need to know is that it's beautifully written and behaves extremely efficiently (actually more efficient than you can probably imagine right now) when treated like a value... particularly if you use move-construction. 所有你需要知道的是,它的编写精美,行为非常有效(实际上比你现在可能想象的更有效)当被视为一个值...特别是如果你使用移动构造。

I feel like I want to copy that string, but I don't know when to decide that. 我觉得我想复制那个字符串,但我不知道何时决定。 Should I copy vectors? 我应该复制载体吗? Sets? 集? Maps? 地图? Entire JSON files...? 整个JSON文件......?

Yes. 是。 A well-written class has "value semantics" (this means it's designed to be treated like a value) - therefore copied and moved. 一个编写良好的类具有“值语义”(这意味着它被设计为被视为一个值) - 因此被复制和移动。

Once upon a time, when I was first writing code, pointers were often the most efficient way to get a computer to do something quickly. 曾几何时,当我第一次编写代码时,指针通常是使计算机快速执行某些操作的最有效方法。 These days, with memory caches, pipelines and prefetching, copying is almost always faster. 现在,通过内存缓存,管道和预取,复制几乎总是更快。 (yes, really!) (对真的!)

In a multi-processor environment, copying is very much faster in all but the most extreme cases . 在多处理器环境中, 除了最极端的情况外 ,复制速度要快得多

If I have a 10 megabyte file as a const string, I really don't want to copy that. 如果我有一个10兆字节的文件作为常量字符串,我真的不想复制它。

If you need a copy of it, then copy it. 如果您需要它的副本,请复制它。 If you really just mean to move it, then std::move it. 如果你真的只想移动它,那么std::move它。

If I'm newing up 100 objects, passing a 5 character const string into each one's constructor, none of them ought to have ownership. 如果我正在新建100个对象,将5个字符的const字符串传递给每个构造函数,那么它们都不应该拥有所有权。 Probably just take a copy of the string. 可能只是拿一个字符串的副本。

A 5-character string is so cheap to copy that you should not even think about it. 一个5个字符的字符串复制起来很便宜,你甚至不应该考虑它。 Just copy it. 只需复制它。 Believe it or not, std::string is written with the full knowledge that most strings are short, and they're often copied. 信不信由你, std::string是在充分了解大多数字符串很短的情况下编写的,并且它们经常被复制。 There won't even be any memory allocation involved . 甚至不会涉及任何内存分配

So (assuming I'm not completely wrong) it's obvious what to do from outside the class, but when you're designing class GenericTextHaver, how do you decide the method of text-having? 所以(假设我并非完全错误)很明显该从课外做什么,但是当你设计类GenericTextHaver时,你如何决定文本的方法呢?

Express the code in the most elegant way you can that succinctly conveys your intent. 以最优雅的方式表达代码,简洁地传达您的意图。 Let the compiler make decisions about how the machine code will look - that it's job. 让编译器决定机器代码的外观 - 这是它的工作。 Hundreds of thousands of people have given their time to ensure that it does that job better than you ever will. 成千上万的人已经花时间确保它比以往更好地完成这项工作。

If all you need is a class that takes a const string in its constructor, and allows you to get a const string with the same value out of it, how do you decide how to represent it internally? 如果您只需要一个在其构造函数中包含const字符串的类,并允许您从中获取具有相同值的const字符串,那么您如何决定如何在内部表示它?

In almost all cases, store a copy. 几乎在所有情况下,都要存储副本。 If 2 instances actually need to share the same string then consider something else, like a std::shared_ptr . 如果2个实例实际上需要共享相同的字符串,那么请考虑其他内容,例如std::shared_ptr But in that case, they probably would not only need to share a string so the 'shared state' should be encapsulated in some other object (ideally with value semantics!) 但在这种情况下,他们可能不仅需要共享一个字符串,所以'共享状态'应该被封装在一些其他对象中(理想情况下是值语义!)

OK, stop talking - show me how the class should look 好的,别说了 - 告诉我课程应该怎么样

class X {
public:

    // either like this - take a copy and move into place
    X(std::string s) : s(std::move(s)) {}

   // or like this - which gives a *miniscule* performance improvement in a
   // few corner cases
/*
   X(const std::string& s) : s(s) {}  // from a const ref
   X(std::string&& s) : s(std::move(s)) {}  // from an r-value reference
*/

  // ok - you made _s const, so this whole class is now not assignable
  const std::string s;

  // another way is to have a private member and a const accessor
  // you will then be able to assign an X to another X if you wish

/*    
  const std::string& value() const {
    return s;
  }

private:
  std::string s;
*/
}; 

If the constructor truly "takes a string and stores it ", then of course your class needs to contain a std::string data member. 如果构造函数真正“接受一个字符串并存储它 ”,那么你的类当然需要包含一个std::string数据成员。 A pointer would only point at some other string that you don't actually own, let alone "store": 指针只会指向你实际上并不拥有的其他字符串,更不用说“存储”了:

struct X
{
    explicit X(std::string s) : s_(std::move(s)) {}

    std::string s_;
};

Note that since we're taking ownership of the string, we may as well take it by value and then move from the constructor argument. 请注意,既然我们取得了字符串的所有权,我们也可以按值获取它,然后从构造函数参数中移除。

In most cases you will want to be copying by value. 在大多数情况下,您需要按值复制。 If the std::string gets destroyed outside of X , X will not know about it and result in undesired behavior. 如果std::stringX之外被破坏, X将不知道它并导致不希望的行为。 However, if we want to do this without taking any copies, a natural thing to do might be to use std::unique_ptr<std::string> and use the std::move operator on it: 但是,如果我们想在不带任何副本的情况下这样做,自然要做的就是使用std::unique_ptr<std::string>并在其上使用std::move运算符:

class X {
public:
    std::unique_ptr<std::string> m_str;
    X(std::unique_ptr<std::string> str)
      : m_str(std::move(str)) { }
}

By doing this, note that the original std::unique_ptr will be empty. 通过执行此操作,请注意原始std :: unique_ptr将为空。 The ownership of the data has been transferred. 数据的所有权已转移。 The nice thing about this is that it protects the data without needing the overhead of a copy. 关于这一点的好处是它可以保护数据而不需要副本的开销。

Alternately, if you still want it accessible from the outside world, you can use an std::shared_ptr<std::string> instead, though in this case care must be taken. 或者,如果您仍希望从外部世界访问它,则可以使用std::shared_ptr<std::string> ,但在这种情况下必须小心。

Yes, generally speaking, it is fine to have a class that holds a pointer to an object but you will need to implement a more complex behaviour in order to make your class safe. 是的,一般来说,拥有一个包含指向对象的指针的类是可以的,但是为了使您的类安全,您需要实现更复杂的行为。 First, as one of the previous responders noticed it is dangerous to keep a pointer to the outside string as it can be destroyed without the knowledge of the class X. This means that the initializing string must be copied or moved when an instance of X is constructed. 首先,正如之前的响应者之一注意到保持指向外部字符串的指针是危险的,因为它可以在不知道类X的情况下被销毁。这意味着当X的实例是X时,必须复制或移动初始化字符串。建造。 Secondly, since the member Xs now points to the string object allocated on the heap (with operator new), the class X needs a destructor to do the proper clean-up: 其次,由于成员Xs现在指向堆上分配的字符串对象(使用operator new),因此类X需要析构函数来进行正确的清理:

class X {
public:
  X(const string& val) {
    cout << "copied " << val << endl;
    s = new string(val);
  }

  X(string&& val) {
    cout << "moved " << val << endl;
    s = new string(std::move(val));
  }

  ~X() {
    delete s;
  }

private:
  const string *s;
};

int main() {
  string s = "hello world";
  X x1(s); // copy
  X x2("hello world"); // move

  return 0;
}

Note, that theoretically you can have a constructor that takes a const string* as well. 注意,理论上你可以有一个构造函数,它也带有一个const字符串*。 It will require more checks (nullptr), will support only copy semantics, and it may look as follows: 它需要更多的检查(nullptr),只支持复制语义,它可能如下所示:

X(const string* val) : s(nullptr) {
    if(val != nullptr) 
      s = new string(*val);
}

These are techniques. 这些都是技术。 When you design your class the specifics of the problem at hand will dictate whether to have a value or a pointer member. 在设计类时,手头问题的具体细节将决定是否有值或指针成员。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM