如何声明一个存储引用返回的对象的变量?

时间:2013-01-07 14:54:18

标签: c++ reference return return-type

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

由于两者都是正确的语法:两种变体之间是否存在显着差异?

3 个答案:

答案 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