C ++引用仍然让我感到困惑。假设我有一个函数/方法,它创建一个Foo
类型的对象并通过引用返回它。 (我假设如果我想返回对象,它不能是在堆栈上分配的局部变量,所以我必须在堆上用new
分配它):
Foo& makeFoo() {
...
Foo* f = new Foo;
...
return *f;
}
当我想存储在另一个函数的局部变量中创建的对象时,类型应该是Foo
void useFoo() {
Foo f = makeFoo();
f.doSomething();
}
或Foo&
?
void useFoo() {
Foo& f = makeFoo();
f.doSomething();
}
由于两者都是正确的语法:两种变体之间是否存在显着差异?
答案 0 :(得分:5)
是的,第一个将复制返回的引用,而第二个将引用makeFoo
的返回。
请注意,使用第一个版本会导致内存泄漏(最有可能),除非你在复制构造函数中做了一些黑魔法。
嗯,除非你拨打delete &f;
,否则第二个会导致泄密。
底线:不要。只需跟随人群并按价值返回。或智能指针。
答案 1 :(得分:3)
你的第一个代码做了很多工作:
void useFoo() {
Foo f = makeFoo(); // line 2
f.doSomething();
}
考虑第2行,会发生一些有趣的事情。首先,编译器将使用类的默认构造函数发出代码以在Foo
构造f
对象。然后,它将调用makeFoo()
,它还会创建一个新的Foo
对象并返回对该对象的引用。编译器还必须发出代码,将makeFoo()
的临时返回值复制到f
的对象中,然后它将销毁临时对象。第2行完成后,将调用f.doSomething()
。但是在useFoo()
返回之前,我们也会销毁f
处的对象,因为它超出了范围。
你的第二个代码示例效率更高,但它实际上可能是错误的:
void useFoo() {
Foo& f = makeFoo(); // line 2
f.doSomething();
}
在该示例中考虑第2行,我们意识到我们不为f
创建对象,因为它只是一个引用。 makeFoo()
函数返回它新分配的对象,我们保留对它的引用。我们通过该引用致电doSomething()
。但是当useFoo()
函数返回时,我们永远不会销毁makeFoo()
为我们创建的新对象并且它会泄漏。
有几种不同的方法可以解决这个问题。如果您不介意额外的构造函数,创建,复制和销毁,您可以使用第一个代码片段中的引用机制。 (如果你有简单的构造函数和析构函数,并且没有太多(或没有)状态要复制,那么它并不重要。)你可以只返回一个指针,它具有强烈的暗示,调用者负责管理生命引用对象的循环。
如果你返回一个指针,你暗示调用者必须管理对象的生命周期,但你没有强制执行它。总有一天,有人会在某个地方弄错。因此,您可以考虑创建一个管理引用的包装类,并提供访问器来封装对象的管理。 (如果你愿意,你甚至可以将它烘焙到Foo
类中。)这种类型的包装类在其通用形式中称为“智能指针”。如果您正在使用STL,您将在the std::unique_ptr
template class中找到智能指针实现。
答案 2 :(得分:2)
函数永远不应该返回对创建的新对象的引用。在创建新值时,应返回值或指针。返回值几乎总是首选,因为几乎所有编译器都将使用RVO / NRVO来删除额外的副本。
返回一个值:
Foo makeFoo(){
Foo f;
// do something
return f;
}
// Using it
Foo f = makeFoo();
返回指针:
Foo* makeFoo(){
std::unique_ptr<Foo> p(new Foo()); // use a smart pointer for exception-safety
// do something
return p.release();
}
// Using it
Foo* foo1 = makeFoo(); // Can do this
std::unique_ptr<Foo> foo2(makeFoo()); // This is better