我首先说我已经读过这个主题: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: " << ©1 << endl;
cout << "copy2 addr: " << ©2 << endl;
cout << "copy3 addr: " << ©3 << endl;
return orig;
}
int main() {
string ret = f();
cout << "ret addr: " << &ret << endl;
}
我得到了以下信息:
原始地址:0x7ffccb085230
副本1地址:0x7ffccb0851a0
复制2地址:0x7ffccb0851c0
复制3地址:0x7ffccb0851e0
ret地址:0x7ffccb085230
您可以看到orig
和ret
指向内存中的同一个字符串实例,因此orig
是通过引用返回的。copy1
、copy2
、copy3
是orig
的副本,因为它们指向内存中的不同对象。