【发布时间】:2016-10-26 09:23:27
【问题描述】:
在下面的代码中,一个字符串被封装在一个类 Foo 中。
对 Foo::getText() 的调用按值返回字符串。这会创建字符串对象的第二个实例,但两个字符串对象现在都指向堆上的同一个字符缓冲区。
当Foo实例被删除时,封装后的字符串会被自动销毁,因此堆上的char缓冲区也会被删除。
即使getText() 按值返回字符串,生成的字符串对象仍然依赖于原始字符串对象的生命周期,以保留它们相互指向的字符缓冲区。
这是否意味着将retval 打印到终端是对已在堆上释放的内存的无效访问?
class Foo
{
Foo(const char* text) :
str(text)
{
}
std::string getText()
{
return str;
}
std::string str;
};
int main()
{
Foo pFoo = new Foo("text");
std::string retval = foo.getText();
delete pFoo;
cout << retval; // invalid memory access to char buffer?
}
我想很多人认为,因为字符串是按值返回的,所以他们不需要关心 Foo 中原始字符串的生命周期。这个问题与字符串并不严格相关,但实际上适用于任何具有封装指针的类,这些指针在销毁时会被释放。但是对于字符串,这里的最佳做法是什么?
- 从不按值返回字符串?
- 如果原始字符串的生命周期得到保证,则仅按值返回字符串?
- 总是复制字符串?
return std::string(retval.c_str()); - 与
getText()的调用者强制签订合同?
编辑:
我想我被 RVO 误导了。此示例中的所有三个字符串都在同一地址返回一个 c_str。是 RVO 的错吗?
class Obj
{
public:
Obj() : s("text")
{
std::printf("%p\n", s.c_str());
}
std::string getText() { return s; }
std::string s;
};
int main()
{
Obj* pObj = new Obj();
std::string s1(pObj->getText());
std::string s2 = pObj->getText();
delete pObj;
std::printf("%p\n", s1.c_str());
std::printf("%p\n", s2.c_str());
}
结果:
0x600022888
0x600022888
0x600022888
【问题讨论】:
-
@πάντα ῥεῖ 这是关于
std::string,而不是std::vector。另一个笨蛋小姐。 -
即使 getText() 按值返回字符串,生成的字符串对象仍然依赖于原始字符串对象的生命周期,以保留它们相互指向的字符缓冲区。——什么??你从哪里得到这个,或者你在猜测? C++ 不是 Java,如果您使用 Java 作为参考来了解 C++ 的工作原理。
-
但是两个字符串对象现在都指向堆上的同一个字符缓冲区。 -- 如果这个类的编码就像很多新手在自制字符串类中的尝试一样错误或缺少复制构造函数、赋值运算符和析构函数,那么是的,它可以错误地指向同一个缓冲区。然而,我们谈论的是由专业人士和专家编写的“std::string”,因此具有正确的复制语义。
-
@PaulMcKenzie 我在上面做了实验来得出这个结论。为什么有人会编写自己的字符串类?
-
很确定你是正确的,他们共享一个缓冲区,至少对于 4.8-4.9 变种的 GCC,但我知道在你引入更改后它们是否不一样。标准分配器非常明亮。如果我没记错的话,在 C++11 之前,不能保证
c_str会返回任何类似于内部表示的东西。
标签: c++ string memory-management return-by-value