【问题标题】:C++: Return std::string reference from stack memoryC++:从堆栈内存返回 std::string 引用
【发布时间】:2011-06-17 16:19:38
【问题描述】:

首先我会说我已经阅读过这个主题:C++ Return reference / stack memory。但是在那里,问题在于std::vector<int> 作为对象类型。但我虽然std::string 的行为不同。这个类不是专门为使用字符串而设计的,而不必担心内存泄漏和错误使用内存吗?

所以,我已经知道这是错误的:

std::vector<t> &function()
{
    vector<t> v;
    return v;
}

但这也错了吗?

std::string &function()
{
    string s = "Faz";
    s += "Far";
    s += "Boo";
    return s;
}

谢谢


额外问题(编辑):那么,当我说返回(按值)std::string 不会复制字符序列时,我是否正确指向char * 数组的指针和一个t_size 的长度?

如果这句话是正确的,这是否是创建一个字符串的深拷贝的有效方法(避免替换会改变字符串)?

string orig = "Baz";
string copy = string(orig);

【问题讨论】:

  • 回答你的额外问题:不,你有错误的想法。创建std::string 的副本始终会创建一个副本。这个问题的答案正确指出的是,使用 RVO,可以正确编写函数(返回值)并避免首先创建副本。请参阅下面@Martinho 的回答:没有副本!

标签: c++ memory-management reference


【解决方案1】:

类型无关紧要;对于任何对象类型T,这种模式总是完全错误的,100% 错误:

T& f() {
    T x;
    return x;
}   // x is destroyed here and the returned reference is thus unusable

如果你从一个函数返回一个引用,你必须确保它所引用的对象在函数返回后仍然存在。由于具有自动存储持续时间的对象在声明它们的块的末尾被销毁,因此保证它们在函数返回后存在。

【讨论】:

  • 感谢我第一个的正确答案,但我接受了另一个答案,因为 Blindy 帮助我解决了额外的问题。但你肯定有我的 +1!
【解决方案2】:

你真的很接近让这些功能发挥作用:

std::string function()
{
    string s = "Faz";
    s += "Far";
    s += "Boo";
    return s;
}

只需让它们返回一个副本而不是一个引用,就可以了。这就是您想要的,基于堆栈的字符串的副本。

它也变得更好了,因为返回值优化 (RVO) 只会创建一次字符串并返回它,就像您在堆上创建它并返回对它的引用一样,所有这些都在幕后!

【讨论】:

  • @Blindy:谢谢,我知道这是一个解决方案,但我在考虑性能。
  • @Martijn,RVO 使其与引用调用一样快,因为返回 幕后的引用。
  • @Blindy:所以,您的评论“因为返回是幕后的参考。”是我额外问题的答案吗?
  • @Martijn,不,您的额外问题的答案是“是的,但这无关紧要”。 RVO 仅用于从函数按值(非引用)传递的返回值。绝对不涉及复制。 string(otherstring) 确实返回了一个深拷贝(至少修改一次),但它使用了拷贝构造函数。
  • @Martijn,同样,绝对不涉及任何类型的复制,无论是指针、引用还是本机类型(在您的示例中为 size_t)。没有任何。这就是重点,避免出于性能原因进行任何复制。
【解决方案3】:

不返回引用,按值返回:

std::string function() // no ref
{
    string s = "Faz";
    s += "Far";
    s += "Boo";
    return s;
}

如果您的编译器可以进行命名返回值优化,也就是 NRVO(这很可能),它会将其转换为大致相当于以下内容的内容,从而避免任何无关的副本:

// Turn the return value into an output parameter:
void function(std::string& s)
{
    s = "Faz";
    s += "Far";
    s += "Boo";
}

// ... and at the callsite,
// instead of:
std::string x = function();
// It does this something equivalent to this:
std::string x; // allocates x in the caller's stack frame
function(x); // passes x by reference

关于额外的问题:

string 的拷贝构造函数总是做一个深拷贝。因此,如果涉及副本,则不存在混叠问题。但是当使用 NRVO 按值返回时,正如您在上面看到的,不会复制。

您可以使用几种不同的语法进行复制:

string orig = "Baz";
string copy1 = string(orig);
string copy2(orig);
string copy3 = orig;

第二个和第三个没有语义上的区别:它们都只是初始化。第一个通过显式调用复制构造函数来创建一个临时变量,然后用一个副本初始化变量。但是编译器可以在这里进行复制省略(而且很可能会这样做)并且只会制作一个副本。

【讨论】:

    【解决方案4】:

    这个问题(不管是什么类型)是你返回的内存引用一旦被命中就会超出范围。

    std::string &function()
    {
        string s = "Faz";
        s += "Far";
        s += "Boo";
    
        // s is about to go out scope here and therefore the caller cannot access it
        return s;
    }
    

    您可能希望将返回类型更改为不是引用而是按值,因此会返回 s 的副本。

    std::string function()
    {
        string s = "Faz";
        s += "Far";
        s += "Boo";
    
        // copy of s is returned to caller, which is good
        return s;
    }
    

    【讨论】:

      【解决方案5】:

      可以取返回字符串的地址,与原字符串的地址进行比较,如下图:

      #include <iostream>    
      using namespace std;
      
      string f() {
          string orig = "Baz";
          string copy1 = string(orig);
          string copy2(orig);
          string copy3 = orig;
      
          cout << "orig addr: " << &orig << endl;
          cout << "copy1 addr: " << &copy1 << endl;
          cout << "copy2 addr: " << &copy2 << endl;
          cout << "copy3 addr: " << &copy3 << endl;
          return orig;
      }
      
      int main() {
          string ret = f();
          cout << "ret addr: " << &ret << endl;
      }
      

      我得到了以下信息:

      原地址:0x7ffccb085230
      复制1地址:0x7ffccb0851a0
      复制2地址:0x7ffccb0851c0
      copy3 地址:0x7ffccb0851e0
      ret地址:0x7ffccb085230

      你看到origret指向内存中的同一个字符串实例,所以orig是通过引用返回的。 copy1copy2copy3orig 的副本,因为它们指向内存中的不同对象。

      【讨论】:

        猜你喜欢
        • 2011-03-16
        • 1970-01-01
        • 2021-11-06
        • 2013-01-15
        • 1970-01-01
        • 2018-02-03
        • 2012-11-10
        • 2022-01-18
        • 2011-07-16
        相关资源
        最近更新 更多