因此,在c ++中,如果将函数的返回值赋给const引用,那么该返回值的生命周期将是该引用的范围。 E.g。
MyClass GetMyClass()
{
return MyClass("some constructor");
}
void OtherFunction()
{
const MyClass& myClass = GetMyClass(); // lifetime of return value is until the end
// of scope due to magic const reference
doStuff(myClass);
doMoreStuff(myClass);
}//myClass is destructed
因此,无论您通常将函数的返回值分配给const对象,您都可以将其分配给const引用。在函数中是否有一种情况,您不希望在赋值中使用引用而是使用对象?你为什么要写这一行:
const MyClass myClass = GetMyClass();
编辑:我的问题让几个人感到困惑所以我添加了GetMyClass函数的定义
编辑2:如果你没有读到这个,请不要尝试回答这个问题: http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/
答案 0 :(得分:4)
如果函数返回一个对象(而不是引用),则需要在调用函数中创建一个副本[尽管可能会采取优化步骤,这意味着该对象被直接写入到最终存储的结果存储中,根据“as-if”原则]。
在示例代码const MyClass myClass = GetMyClass();
中,此“复制”对象名为myclass
,而不是存在的临时对象,但未命名(或者除非您查看机器代码,否则可见) 。换句话说,无论你是否为它声明一个变量,在调用MyClass
的函数中都会有一个GetMyClass
对象 - 这只是你是否可见它的问题。
EDIT2:
const
参考解决方案看似相似(不完全相同,这只是为了解释我的意思,你实际上不能这样做):
MyClass __noname__ = GetMyClass();
const MyClass &myclass = __noname__;
只是编译器在幕后生成__noname__
变量,而没有实际告诉你。
通过使const MyClass myclass
使对象可见并且清楚发生了什么(并且GetMyClass
正在返回对象的COPY,而不是对某个已存在的对象的引用)。
另一方面,如果GetMyClass
确实返回了引用,那么它肯定是正确的做法。
在某些编译器中,使用引用甚至可能在使用对象时添加额外的内存读取,因为引用“是指针”[是的,我知道,标准没有说明,但请在抱怨之前帮我一个忙,并告诉我一个编译器,它不会实现引用作为带有额外糖的指针,使它们更甜美],所以要使用引用,编译器应该读取引用值(指向对象的指针)然后从该指针读取对象内部的值。在非引用的情况下,对象本身对编译器“已知”为直接对象,而不是引用,从而节省了额外的读取。当然,大多数编译器会在大部分时间内优化这样的额外参考,但它并不总能做到这一点。
答案 1 :(得分:1)
一个原因是该引用可能会使您的代码的其他读者感到困惑。并非所有人都意识到对象的生命周期延伸到参考范围。
答案 2 :(得分:1)
语义:
MyClass const& var = GetMyClass();
和
MyClass const var = GetMyClass();
非常不同。一般来说,你只会使用
首先,当函数本身返回一个引用时(并且是
需要通过其语义返回引用)。你呢
知道你需要注意的一生
对象(不受你的控制)。你使用第二个
当你想拥有(副本)对象时。使用第二个
在这种情况下是误导,可能会导致意外(如果
function也返回对象的引用
早先被破坏了)并且可能效率稍差
(虽然在实践中,我希望两者都能完全生成
如果GetMYClass
按值返回,则使用相同的代码。)
答案 3 :(得分:0)
我不明白你想要达到的目标。 T const&
可以绑定(在堆栈上)到函数返回的T
(按值)的原因是为了使其他函数可以将此临时作为{{1}参数。这可以防止您创建重载的要求。但无论如何必须构建返回值。
但是今天(使用C ++ 11)你可以使用T const&
。
修改强> 作为可能发生的事情的一个例子,我将介绍一些东西:
const auto myClass = GetMyClass();
MyClass version_a();
MyClass const& version_b();
const MyClass var1 =version_a();
const MyClass var2 =version_b();
const MyClass var3&=version_a();
const MyClass var4&=version_b();
const auto var5 =version_a();
const auto var6 =version_b();
初始化,结果为var1
version_a()
初始化为var2
返回的引用所属对象的副本version_b()
包含对返回的temoprary的const引用并延长其生命周期var3
返回的引用初始化var4
version_b()
与var5
var1
与var6
他们的语义完全不同。 var4
因我上面给出的原因而起作用。只有var3
和var5
会自动存储返回的内容。
答案 4 :(得分:0)
的效果强> 的
由于大多数当前的编译器都是副本(和移动),因此两个版本的效率应该大致相同:
const MyClass& rMyClass = GetMyClass();
const MyClass oMyClass = GetMyClass();
在第二种情况下,语义上需要复制或移动,但可以按[class.copy] / 31省略。略有不同的是,第一个适用于不可复制的不可移动类型。
Mats Petersson和James Kanze已经指出,对于某些编译器来说,访问引用可能会更慢。
的寿命强> 的
引用应该在整个范围内有效,就像具有自动存储的对象一样。这个“ should ”当然是由程序员强制执行的。因此,对于读者IMO来说,它们所隐含的生命周期没有差异。虽然,如果有错误,我可能会寻找悬空引用(不信任原始代码/参考的终身索赔)。
如果GetMyClass
可以(合理地)改变以返回引用,则必须确保该对象的生命周期足够,例如。
SomeClass* p = /* ... */;
void some_function(const MyClass& a)
{
/* much code with many side-effects */
delete p;
a.do_something(); // oops!
}
const MyClass& r = p->get_reference();
some_function(r);
的所有权强> 的
直接命名像const MyClass oMyClass;
这样的对象的变量清楚地表明我拥有这个对象。考虑mutable
成员:如果您稍后更改它们,那么如果已将其声明为参考,那么读者可以立即清楚(对于所有更改)。
此外,对于参考,它引用的对象不会改变并不明显。 const
引用仅暗示您不会更改对象,而 nobody 不会更改对象(*)。程序员必须知道这个引用是引用该对象的唯一方法,通过查找该变量的定义。
(*)免责声明:尽量避免不明显的副作用
答案 5 :(得分:0)
实际上被调用的析构函数有一个重要的含义。检查Gotw88,Q3和A3。我把所有东西放在一个小的测试程序中(Visual-C ++,原谅stdafx.h)
// Gotw88.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
class A
{
protected:
bool m_destroyed;
public:
A() : m_destroyed(false) {}
~A()
{
if (!m_destroyed)
{
std::cout<<"A destroyed"<<std::endl;
m_destroyed=true;
}
}
};
class B : public A
{
public:
~B()
{
if (!m_destroyed)
{
std::cout<<"B destroyed"<<std::endl;
m_destroyed=true;
}
}
};
B CreateB()
{
return B();
}
int _tmain(int argc, _TCHAR* argv[])
{
std::cout<<"Reference"<<std::endl;
{
const A& tmpRef = CreateB();
}
std::cout<<"Value"<<std::endl;
{
A tmpVal = CreateB();
}
return 0;
}
这个小程序的输出如下:
Reference
B destroyed
Value
B destroyed
A destroyed
这是设置的一个小解释。 B派生自A,但两者都没有虚拟析构函数(我知道这是一个WTF,但这里很重要)。 CreateB()按值返回B. Main现在调用CreateB并首先将此调用的结果存储在类型A的const引用中。然后调用CreateB并将结果存储在类型A的值中。
结果很有趣。首先 - 如果通过引用存储,则调用正确的析构函数(B),如果按值存储,则调用错误的析构函数。第二 - 如果存储在引用中,析构函数只被调用一次,这意味着只有一个对象。按值导致2次调用(对不同的析构函数),这意味着有2个对象。
我的建议 - 使用const引用。至少在Visual C ++上,它可以减少复制。如果您不确定编译器,请使用并调整此测试程序以检查编译器。怎么适应?添加复制/移动构造函数和复制赋值运算符。
我很快添加了副本&amp; A级和A级的分配操作员乙
A(const A& rhs)
{
std::cout<<"A copy constructed"<<std::endl;
}
A& operator=(const A& rhs)
{
std::cout<<"A copy assigned"<<std::endl;
}
(对B而言,只需用B替换每个大写字母A)
这导致以下输出:
Reference
A constructed
B constructed
B destroyed
Value
A constructed
B constructed
A copy constructed
B destroyed
A destroyed
这证实了上面的结果(请注意,B构造为B的构造结果是从A派生的,因此每当调用Bs构造函数时都会调用As构造函数。)
其他测试:Visual C ++也接受非const引用,其结果与本例相同(在本例中)。另外,如果你使用auto作为类型,调用正确的析构函数(当然)并且返回值优化开始,最后它与const引用的结果相同(当然,auto有B类而不是A)