按值返回std :: string是否安全?

时间:2016-06-23 21:04:10

标签: c++ string memory-management return-by-value

在以下代码中,字符串封装在类Foo中。

对Foo :: getText()的调用按值返回字符串。这将创建字符串对象的第二个实例,但现在两个字符串对象都指向堆上的相同char缓冲区。

当删除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()
  5. 签订合同

    编辑:

    我想我被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
    

2 个答案:

答案 0 :(得分:10)

  

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

不,他们不会。

std::string拥有自己的内容。复制std::string时,复制其缓冲区。

  

我认为很多人都认为,因为字符串是按值返回的,所以不必担心Foo中原始字符串的生命周期。

那些人是对的。没有&#34;分享&#34;。

您的价值回报很好,您无需考虑它。

答案 1 :(得分:6)

我想在@ LightnessRacesInOrbit的答案中添加一些要点:

  

永远不要按值返回字符串?

永远不要通过引用或指针返回本地class Program { [STAThread] static void Main(string[] args) { // ... 。实际上,永远不要通过引用或指针返回任何本地。按价值就好了。

  

如果原始字符串的生命周期为,则仅按值返回字符串   保证?

再一次,你在倒退。如果保证原件的使用寿命,则仅通过引用或指针返回

  

始终复制字符串? return std :: string(retval.c_str());

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

  

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

无论如何你都不需要

  

我想很多人都认为,因为字符串是按值返回的,所以不需要关心Foo中原始字符串的生命周期。这个问题与字符串没有严格的关系,但实际上适用于任何具有封装指针的类,这些类在销毁时是免费的。

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