std::string类成员应该是指针吗
Should a std::string class member be a pointer?
为什么/为什么不呢?
假设我有一个类,它在构造函数中获取一个字符串并存储它。这个类成员应该是指针,还是只是一个值?
class X {
X(const std::string& s): s(s) {}
const std::string s;
};
或者。。。
class X {
X(const std::string* s): s(s) {}
const std::string* s;
};
如果我存储一个原始类型,我会复制一个。如果我在存储一个对象,我会使用一个指针。
我觉得我想复制那个字符串,但我不知道什么时候该决定。我应该复制矢量吗?套装?地图?整个JSON文件。。。?
编辑:
听起来我需要阅读移动语义。但不管怎样,我想让我的问题更具体一点:
如果我有一个10兆字节的文件作为常量字符串,我真的不想复制它。
如果我正在新建100个对象,并将一个5个字符的常量字符串传递到每个对象的构造函数中,那么它们都不应该拥有所有权。可能只是复制一个字符串。
所以(假设我没有完全错)很明显,在类的外部对做什么,但当你设计class GenericTextHaver
时,你如何决定文本拥有的方法?
如果你只需要一个在构造函数中使用const字符串的类,并允许你从它的中获得一个具有相同值的const字符串,那么你如何决定如何在内部表示它?
std::string类成员应该是指针吗?
无
为什么不呢?
因为std::string和标准库中的其他所有对象一样,以及c++中其他所有编写良好的对象都被设计为一个值。
它可能在内部使用指针,也可能不使用指针——这与您无关。你所需要知道的是,当它被当作一种价值观对待时,它写得很漂亮,表现得非常高效(实际上比你现在想象的更高效)。。。特别是如果你使用移动结构。
我觉得我想复制那个字符串,但我不知道什么时候该决定。我应该复制矢量吗?套装?地图?整个JSON文件。。。?
是的。一个写得好的类具有"值语义"(这意味着它被设计成像一个值一样对待),因此被复制和移动。
从前,当我第一次写代码时,指针通常是让计算机快速完成任务的最有效方法。如今,有了内存缓存、管道和预取,复制几乎总是更快。(是的,真的!)
在多处理器环境中,除了最极端的情况外,所有情况下的复制都要快得多。
如果我有一个10兆字节的文件作为常量字符串,我真的不想复制它。
如果需要它的副本,那么复制它。如果你真的只是想移动它,那么std::move
它。
如果我正在新建100个对象,并将一个5个字符的常量字符串传递到每个对象的构造函数中,那么它们都不应该拥有所有权。可能只是复制一个字符串。
一个5个字符的字符串复制起来太便宜了,你甚至不应该去想它。只需复制它。信不信由你,std::string
是在充分了解大多数字符串都很短的情况下编写的,而且它们经常被复制。甚至不会涉及任何内存分配。
所以(假设我没有完全错)在类外做什么是显而易见的,但当你设计类GenericTextHaver时,你如何决定文本拥有的方法?
以最优雅的方式表达代码,简洁地传达您的意图。让编译器决定机器代码的外观——这就是它的工作。成千上万的人付出了他们的时间来确保它比你做得更好。
如果你只需要一个在构造函数中使用const字符串的类,并允许你从中获得具有相同值的const字符串,那么你如何决定如何在内部表示它?
在几乎所有情况下,都要存储一份副本。如果两个实例实际上需要共享同一个字符串,那么考虑其他的东西,比如std::shared_ptr
。但在这种情况下,它们可能不仅需要共享一个字符串,因此"共享状态"应该封装在其他对象中(最好是具有值语义!)
好的,别说话了,给我看看这个班应该是什么样子
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;
*/
};
如果构造函数真的"获取字符串并存储它",那么您的类当然需要包含一个std::string
数据成员。指针只会指向其他一些你实际上并不拥有的字符串,更不用说"存储"了:
struct X
{
explicit X(std::string s) : s_(std::move(s)) {}
std::string s_;
};
请注意,由于我们获得了字符串的所有权,我们还可以按值获取它,然后从构造函数参数中移除。
在大多数情况下,您都希望按值进行复制。如果std::string
在X
之外被破坏,X
将不知道它,并导致不期望的行为。然而,如果我们想在不复制的情况下完成此操作,那么自然的做法可能是使用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)) { }
}
通过这样做,请注意原始std::unique_ptr将为空。数据的所有权已经转移。这样做的好处是它可以保护数据,而不需要拷贝的开销。
或者,如果您仍然希望从外部世界访问它,则可以使用std::shared_ptr<std::string>
,尽管在这种情况下必须小心。
是的,一般来说,拥有一个指向对象的指针的类是可以的,但为了确保类的安全,您需要实现更复杂的行为。首先,正如前面的一个响应者所注意到的那样,保留指向外部字符串的指针是危险的,因为它可能在不知道类X的情况下被破坏。这意味着在构造X的实例时,必须复制或移动初始化字符串。其次,由于成员X.s现在指向堆上分配的字符串对象(带有运算符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;
}
请注意,从理论上讲,您也可以使用一个const字符串*的构造函数。它将需要更多的检查(nullptr),只支持复制语义,看起来可能如下:
X(const string* val) : s(nullptr) {
if(val != nullptr)
s = new string(*val);
}
这些都是技巧。在设计类时,手头问题的细节将决定是要有值还是要有指针成员。
- 如果基类包含双指针成员,则派生类的构造函数
- C++正确的指针成员初始化
- 是否可以使用智能指针成员设置具有另一个结构的结构?
- 为什么 operator() 处的指针成员不起作用?
- 更改队列指针成员的值需要在 C++ 中出现奇怪的错误
- 如何从另一个嵌套类中调用某个封闭类的嵌套类的函数指针成员的值?
- 结构对象的指针成员在传递给函数时被修改
- 参数的混合值,当我调用指针成员函数时
- 如何正确使用结构的共享指针成员?
- C++:私有类指针成员返回未定义的值
- 在函数中传递带有指针成员的结构是浅拷贝或深拷贝在 C 中
- 包含指针成员的嵌套结构
- 在 c++ 中,为什么 -> 被称为二进制中缀指针成员访问运算符?
- C++ 类指针成员行为奇怪(错误)
- 常量结构的指针成员
- 在类C++指针成员中
- 指针成员未在复制构造函数中初始化
- C++移动拥有指针成员的构造函数
- C++ 包含唯一指针成员变量的类的赋值运算符
- 如何使用 QPoint 指针成员对类进行排队和取消排队