C++:从堆栈内存返回std::字符串引用

C++: Return std::string reference from stack memory

本文关键字:std 字符串 引用 返回 内存 堆栈 C++      更新时间:2023-10-16

我首先说我已经读过这个主题:C++返回引用/堆栈内存。但是,问题是使用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;
}

感谢


附加问题(EDIT):那么,当我说返回(按值)一个std::string不复制字符序列,只复制一个指向char *数组的指针和一个长度为size_t时,我说得对吗?

如果这个语句是正确的,那么这是创建字符串的深层副本的有效方法吗(以避免同时操作两个字符串)?

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

类型是什么并不重要;对于任何对象类型T:,这种模式总是完全、100%错误的

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

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

您已经非常接近于使这些功能发挥作用:

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

只要让他们返回一份副本而不是一份参考,你就做好了准备。这就是您想要的,一个基于堆栈的字符串的副本。

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

不返回引用,按值返回:

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

关于额外的问题:

字符串的复制构造函数总是进行深度复制。因此,如果涉及到副本,就不会出现混叠问题。但是,当使用NRVO按值返回时,正如您在上面看到的,不会进行复制。

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

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

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

此操作的问题(无论类型如何)是,返回的内存引用在返回时超出范围。

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;
}

您可以获取返回字符串的地址,并将其与原始字符串的地址进行比较,如下所示:

#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
复制3地址:0x7ffccb0851e0
ret地址:0x7ffccb085230

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