一个非const引用如何不能绑定到一个临时对象?
为什么不允许获取非const引用临时对象,函数getx()
返回? 显然,这是C ++标准所禁止的,但我对这种限制的目的感兴趣, 而不是对标准的引用 。
struct X { X& ref() { return *this; } }; X getx() { return X();} void g(X & x) {} int f() { const X& x = getx(); // OK X& x = getx(); // error X& x = getx().ref(); // OK g(getx()); //error g(getx().ref()); //OK return 0; }
- 很显然,对象的生命周期不可能是原因,因为对C ++ Standard 不禁止引用对象。
- 很明显,在上面的例子中临时对象是不固定的,因为允许调用非常量函数。 例如,
ref()
可以修改临时对象。 - 另外,
ref()
允许你愚弄编译器并获得一个到这个临时对象的链接,并且解决了我们的问题。
此外:
他们说“给const引用分配一个临时对象可以延长这个对象的生命周期”和“没有关于非const引用的说法”。 我另外的问题 。 下面的赋值是否延长了临时对象的生命周期?
X& x = getx().ref(); // OK
从这个关于右值引用的Visual C ++博客文章 :
C ++不希望你意外地修改临时对象,而是直接调用一个可修改右值的非const成员函数是明确的,所以它是允许的…
基本上,你不应该试图修改临时对象,因为他们是临时对象,现在就会死的。 允许调用非const方法的原因是,只要你知道自己在做什么,而且你是明确的(比如使用reinterpret_cast),就可以做一些“愚蠢”的事情。 但是,如果你将一个临时对象绑定到一个非const引用上,你可以不断地将它传递给“永远”,只是为了让对象的操作消失,因为在这个过程中你完全忘记了这是一个暂时的。
如果我是你,我会重新思考我的functiondevise。 为什么g()接受引用,是否修改参数? 如果不是,请把它作为const引用,如果是的话,为什么你试图通过临时的,你不在意这是一个临时的修改吗? 为什么getx()返回临时呢? 如果您与我们分享您的真实情况以及您正在努力完成的任务,那么您可以就如何实现这一目标得到一些很好的build议。
违背语言和愚弄编译器很less能解决问题 – 通常会产生问题。
编辑:在评论中解决问题:1) X& x = getx().ref(); // OK when will x die?
X& x = getx().ref(); // OK when will x die?
– 我不知道也不在乎,因为这正是我所说的“违背语言”的意思。 该语言说:“临时死亡声明的结尾,除非他们必须被强制参考,在这种情况下,当参考超出范围时,他们死亡”。 应用这个规则,看起来x在下一条语句的开头就已经死了,因为它没有绑定到const引用(编译器不知道ref()返回什么)。 但这只是一个猜测。
2)我清楚地expression了目的:不允许修改临时对象,因为它没有任何意义(忽略C ++ 0x右值引用)。 “那么为什么我允许打电话给非常量的成员呢? 是一个很好的答案,但我没有比我上面已经提到的更好的答案。
3)那么,如果我是正确的关于x在X& x = getx().ref();
在陈述结束时死去,问题是显而易见的。
无论如何,基于你的问题和意见,我不认为即使这些额外的答案会满足你。 下面是最后的尝试/总结:C ++委员会认为修改临时对象是没有意义的,因此,它们不允许绑定到非const引用。 可能是一些编译器的实现或历史问题也涉及到,我不知道。 然后,出现了一些具体的案例,并决定不pipe多大,他们仍然会允许通过调用非const方法进行直接修改。 但是,这是一个例外 – 你通常不允许修改临时表。 是的,C ++通常很奇怪。
在你的代码中, getx()
返回一个临时对象,即所谓的“右值”。 您可以将rvalues复制到对象(也就是variables)中,或者将它们绑定到const引用(这会延长它们的生命周期直到引用的生命周期结束)。 你不能将rvalues绑定到非const引用。
这是一个慎重的devise决定,以防止用户意外修改在expression式结尾处死亡的对象:
g(getx()); // g() would modify an object without anyone being able to observe
如果你想这样做,你必须首先创build一个本地副本或对象,或者将其绑定到一个const引用:
X x1 = getx(); const X& x2 = getx(); // extend lifetime of temporary to lifetime of const reference g(x1); // fine g(x2); // can't bind a const reference to a non-const reference
请注意,下一个C ++标准将包含右值引用。 你所知道的参考因此被称为“左值参考”。 您将被允许将右值绑定到右值引用,并且可以在“右值”上重载函数:
void g(X&); // #1, takes an ordinary (lvalue) reference void g(X&&); // #2, takes an rvalue reference X x; g(x); // calls #1 g(getx()); // calls #2 g(X()); // calls #2, too
rvalue引用背后的想法是,由于这些对象将会死亡,您可以利用这些知识并实现所谓的“移动语义”,这是一种优化:
class X { X(X&& rhs) : pimpl( rhs.pimpl ) // steal rhs' data... { rhs.pimpl = NULL; // ...and leave it empty, but deconstructible } data* pimpl; // you would use a smart ptr, of course }; X x(getx()); // x will steal the rvalue's data, leaving the temporary object empty
你显示的是操作符链接是允许的。
X& x = getx().ref(); // OK
expression式是'getx()。ref();' 并在分配给“x”之前执行完成。
请注意,getx()不会返回引用,而是将完全形成的对象返回到本地上下文中。 该对象是临时的,但它不是常量,因此可以调用其他方法来计算值或发生其他副作用。
// It would allow things like this. getPipeline().procInstr(1).procInstr(2).procInstr(3); // or more commonly std::cout << getManiplator() << 5;
看一下这个答案的最后一个更好的例子
你不能将一个临时对象绑定到一个引用上,因为这样做会产生一个对象的引用,这个对象将在expression式的末尾被销毁,从而给你一个悬而未决的参考(这是不整洁的,标准不喜欢不整洁)。
ref()返回的值是一个有效的引用,但是该方法不关注它返回的对象的生命周期(因为它的上下文中不能包含这些信息)。 你基本上做了相当于:
x& = const_cast<x&>(getX());
使用对临时对象的常量引用来执行此操作的原因是,标准将临时对象的生命周期延长到引用的生命周期,因此临时对象的使用期限会延长到语句结束之后。
所以剩下的唯一问题是,为什么标准不希望引用临时对象来延长对象的寿命,直到声明结束?
我相信这是因为这样做会使编译器很难为临时对象获得正确的。 这是为了const对临时对象的引用而完成的,因为这具有有限的用法,因此迫使你创build对象的副本来做任何有用的事情,但确实提供了一些有限的function。
想想这种情况:
int getI() { return 5;} int x& = getI(); x++; // Note x is an alias to a variable. What variable are you updating.
延长这个临时对象的使用寿命将会非常混乱。
虽然如下:
int const& y = getI();
会给你的代码,它是直观的使用和理解。
如果你想修改这个值,你应该把这个值返回给一个variables。 如果您试图避免从函数中复制obejct的代价(因为它似乎是复制构造回来的(技术上是这样的))。 那么不要打扰编译器非常擅长“返回值优化”
主要问题是这样的
g(getx()); //error
是一个逻辑错误: g
正在修改getx()
的结果,但是您没有任何机会检查修改的对象。 如果g
不需要修改它的参数,那么它就不需要左值引用,它可以通过值或const引用来获取参数。
const X& x = getx(); // OK
是有效的,因为你有时需要重用expression式的结果,而且很清楚你正在处理一个临时对象。
但是这是不可能的
X& x = getx(); // error
而不会使g(getx())
有效,这正是语言devise人员首先想要避免的。
g(getx().ref()); //OK
是有效的,因为方法只知道这个的常量,他们不知道它们是在左值还是在右值被调用。
和C ++一样,你有一个解决这个规则的方法,但是你必须通过明确地告诉编译器你知道你在做什么:
g(const_cast<x&>(getX()));
看起来像原来的问题, 为什么这是不允许的已明确回答:“因为这很可能是一个错误”。
FWIW,我想我会展示如何做,即使我不认为这是一个很好的技术。
我有时想通过一个非const引用的方法来传递一个临时对象的原因是故意抛出一个由引用返回的值,调用方法并不关心。 像这样的东西:
// Assuming: void Person::GetNameAndAddr(std::string &name, std::string &addr); string name; person.GetNameAndAddr(name, string()); // don't care about addr
正如在前面的答案中所解释的那样,这不会被编译 但是这个编译和工作正确(用我的编译器):
person.GetNameAndAddr(name, const_cast<string &>(static_cast<const string &>(string())));
这只是表明你可以使用转换来骗人的编译器。 显然,声明并传递一个未使用的自动variables会更清晰:
string name; string unused; person.GetNameAndAddr(name, unused); // don't care about addr
这种技术在方法的作用域中引入了一个不需要的局部variables。 如果出于某种原因想要防止它在方法中稍后被使用,例如为了避免混淆或错误,可以将其隐藏在本地块中:
string name; { string unused; person.GetNameAndAddr(name, unused); // don't care about addr }
– 克里斯
为什么在C ++ FAQ (我的粗体 )中讨论:
在C ++中,非const引用可以绑定到左值,const引用可以绑定到左值或右值,但是没有任何东西可以绑定到非常量右值。 这是为了保护人们在改变使用新价值之前销毁的临时人员的价值 。 例如:
void incr(int& a) { ++a; } int i = 0; incr(i); // i becomes 1 incr(0); // error: 0 is not an lvalue
如果这个增量(0)被允许,或者是一些暂时没有人看到的增加或者更糟 – 0的值将变成1.后者听起来很愚蠢,但是在早期的Fortran编译器中实际上有一个类似的错误在内存位置旁边保存值0。
为什么你会想X& x = getx();
? 只需使用X x = getx();
并依靠RVO。
邪恶的解决方法涉及“可变”的关键字。 实际上,邪恶是留给读者的一个练习。 或者看看这里: http : //www.ddj.com/cpp/184403758
优秀的问题,这是我的一个更简洁的答案的尝试(因为很多有用的信息在评论中,很难挖掘噪音)。
任何直接与临时性有关的参考将延长其使用寿命[12.2.5]。 另一方面,用另一个引用初始化的引用不会 (即使它最终是相同的临时的)。 这是有道理的(编译器不知道最终引用的是什么)。
但是这个想法是非常混乱的。 例如const X &x = X();
将使临时的最后只要x
引用,但是const X &x = X().ref();
不会(谁知道什么ref()
实际返回)。 在后一种情况下, X
的析构函数在这一行的末尾被调用。 (这是可观的与一个不平凡的析构函数。)
所以看起来总是令人困惑和危险(为什么复杂的关于对象生命周期的规则?),但可能至less需要const引用,所以标准确实为他们设置了这种行为。
[来自sbi评论]:请注意,绑定到一个const引用增强临时的生命周期的事实是一个故意添加的exception(TTBOMK为了允许手动优化)。 没有为非const引用添加exception,因为将临时绑定到非const引用看起来很可能是程序员错误。
所有临时对象都会一直持续到完整expression式的结尾。 然而,为了利用它们,你需要像ref()
那样的技巧。 这是合法的。 除了提醒程序员一些不寻常的事情正在发生(即参考参数的修改将会很快失去)之外,似乎还没有一个好的理由来让额外的循环跳过。
[另一个sbi评论] Stroustrup给出(在D&E)不允许rvalues绑定到非const引用的原因是,如果Alexey的g()会修改对象(你期望从一个非const的函数参考),它会修改一个将要死亡的对象,所以没有人可以得到修改的值。 他说这很可能是一个错误。
“很明显,临时对象在上面的示例中不是常量,因为允许调用非常量函数,例如,ref()可以修改临时对象。
在你的例子中,getX()不会返回一个const X,所以你可以像调用X()。ref()一样调用ref()。 你正在返回一个非const的引用,所以可以调用非const的方法,你不能做的是将ref分配给一个非const的引用。
随着SadSidos评论这使你的三点不正确。
我希望分享一个场景,希望我能做Alexey所要求的。 在Maya C ++插件中,我必须执行以下shenanigan以获取节点属性的值:
MFnDoubleArrayData myArrayData; MObject myArrayObj = myArrayData.create(myArray); MPlug myPlug = myNode.findPlug(attributeName); myPlug.setValue(myArrayObj);
写这个很麻烦,于是我写了下面的帮助函数:
MPlug operator | (MFnDependencyNode& node, MObject& attribute){ MStatus status; MPlug returnValue = node.findPlug(attribute, &status); return returnValue; } void operator << (MPlug& plug, MDoubleArray& doubleArray){ MStatus status; MFnDoubleArrayData doubleArrayData; MObject doubleArrayObject = doubleArrayData.create(doubleArray, &status); status = plug.setValue(doubleArrayObject); }
现在我可以从post的开头写下代码:
(myNode | attributeName) << myArray;
问题是它不能在Visual C ++之外编译,因为它试图绑定从|返回的临时variables 运算符到<<运算符的MPlug引用。 我希望它是一个参考,因为这个代码被调用很多次,我宁愿没有MPlug复制这么多。 我只需要临时对象直到第二个函数结束。
那么,这是我的情况。 只是想我会展示一个人们想要做阿列克谢描述的例子。 我欢迎所有的批评和build议!
谢谢。