C++ C++;:从堆栈内存返回std::string引用

C++ C++;:从堆栈内存返回std::string引用,c++,memory-management,reference,C++,Memory Management,Reference,首先,我要说我读过这个主题:。但在这里,问题在于对象类型是std::vector。但是我认为std::string的行为是不同的。这个类不是特别适合使用字符串而不必担心内存泄漏和内存的错误使用吗 所以,我已经知道这是错误的: std::vector<t> &function() { vector<t> v; return v; } 谢谢 额外问题(编辑):那么,我说的返回(按值)一个std::string不复制字符序列,只复制指向char*数组

首先,我要说我读过这个主题:。但在这里,问题在于对象类型是
std::vector
。但是我认为std::string的行为是不同的。这个类不是特别适合使用字符串而不必担心内存泄漏和内存的错误使用吗

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

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


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

如果此语句正确,这是创建字符串深度副本的有效方法吗(以避免替换将改变字符串)


不管是什么类型的;对于任何对象类型
T
,此模式总是完全错误的:

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;
}
只要让他们返回一个副本而不是一个引用,就可以了。这就是您想要的,基于堆栈的字符串的副本


它也变得更好了,因为返回值优化(returnvalueoptimization,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;
第二个和第三个没有语义上的区别:它们都只是初始化。第一个函数通过显式调用copy构造函数创建一个临时变量,然后用一个副本初始化变量。但是编译器可以在这里进行复制省略(很可能会),并且只进行一次复制。

这个问题(无论类型如何)是,您返回的内存引用在返回时超出了范围

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;
}
#包括
使用名称空间std;
字符串f(){
字符串orig=“Baz”;
字符串copy1=字符串(原始);
字符串copy2(orig);
字符串copy3=orig;

cout@Blindy:谢谢,我知道这是一个解决方案,但我一直在考虑性能。@Martijn,RVO使其与引用调用一样快,因为返回是幕后引用。@Blindy:那么,您的评论“因为返回是幕后引用”这是我额外问题的答案吗?@Martijn,不是,你额外问题的答案是“是的,但那是不相关的”。RVO只用于从函数中通过值(非引用)传递的返回值。绝对不涉及复制。
string(otherstring)
确实返回深度副本(至少在修改它之后),但它使用复制构造函数。@Martijn,同样,绝对不涉及任何类型的复制,无论是指针、引用还是本机类型(
size\t
).一点也没有。这就是关键,为了避免出于性能原因而进行任何复制。回答您的额外问题:不,您的想法是错误的。创建
std::string
的副本总是创建副本。这个问题的答案正确地指出,使用RVO,可以正确编写函数(返回一个值)并避免在一开始就创建副本。请参阅下面@Martinho的答案:无副本!感谢您对我的第一个答案的正确回答,但我接受了另一个答案,因为Blindy帮助我解决了额外的问题。但您肯定拥有我的+1!
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;
}