【问题标题】:Is it safe to return a std::string by value?按值返回 std::string 是否安全?
【发布时间】: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 中原始字符串的生命周期。这个问题与字符串并不严格相关,但实际上适用于任何具有封装指针的类,这些指针在销毁时会被释放。但是对于字符串,这里的最佳做法是什么?

  1. 从不按值返回字符串?
  2. 如果原始字符串的生命周期得到保证,则仅按值返回字符串?
  3. 总是复制字符串? return std::string(retval.c_str());
  4. 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


【解决方案1】:

这会创建字符串对象的第二个实例,但两个字符串对象现在都指向堆上的同一个字符缓冲区。

不,他们没有。

std::strings 拥有自己的内容。当您复制 std::string 时,您复制了它的缓冲区。

我想很多人认为,因为字符串是按值返回的,所以他们不需要关心 Foo 中原始字符串的生命周期。

那些人是对的。没有“分享”。

你的价值回报很好,你不必多想。

【讨论】:

    【解决方案2】:

    我想为@LightnessRacesInOrbit 的回答补充几点:

    从不按值返回字符串?

    永远不要通过引用或指针返回本地 strings。实际上,永远不要通过引用或指针返回本地的 anything。按价值计算就好了。

    仅当原始字符串的生命周期为 保证?

    再一次,你在往回想。只有在保证原始生命周期的情况下,才通过引用或指针返回

    总是复制字符串?返回 std::string(retval.c_str());

    如果 C++ 无法将字符串移出 (RVO/NRVO),它会自动为您执行此操作。无需手动复制。

    与 getText() 的调用者强制签订合同?

    反正你得到一份副本就不需要

    我想很多人认为,因为字符串是按值返回的,所以他们不需要关心 Foo 中原始字符串的生命周期。这个问题与字符串并不严格相关,但确实适用于任何具有封装指针且在销毁时释放的类。

    他们的假设是正确的。任何具有(工作)复制构造函数的类都可以根据需要经常复制,并且每个复制的实例都完全独立于其他实例。复制构造函数负责复制堆空间。

    【讨论】:

    • 感谢您在回答中付出了一些努力。
    猜你喜欢
    • 2023-03-26
    • 2014-01-05
    • 2020-03-09
    • 2012-06-02
    • 2016-09-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-07
    相关资源
    最近更新 更多