为什么非const引用不能绑定到临时对象?

为什么不允许获取临时对象的非const引用? getx()返回哪个函数?显然,这是c++标准所禁止的 但我感兴趣的是这种限制的目的,不是参考的标准。< / p >
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;
}
  1. 很明显,对象的生存期不能是原因,因为 在c++标准中,对对象的常量引用是不禁止
  2. 很明显,在上面的示例中,临时对象不是常量,因为允许调用非常量函数。例如,ref()可以修改临时对象。
  3. 此外,ref()允许你欺骗编译器并获得到这个临时对象的链接,这就解决了我们的问题。

此外:

他们说“给const引用赋值一个临时对象可以延长这个对象的生命周期”和“尽管没有提到非const引用”。 我的额外的问题。下面的赋值是否会延长临时对象的生命周期?< / p >
X& x = getx().ref(); // OK
107814 次浏览

为什么你会想要X& x = getx();?只需使用X x = getx();并依赖RVO。

很明显,在上面的示例中,临时对象不是常量,因为调用 对于非常数函数是允许的。例如,ref()可以修改临时的 对象。" < / p >

在你的例子中,getX()不会返回一个const X,所以你可以像调用X().ref()一样调用ref()。你正在返回一个非const引用,因此可以调用非const方法,你不能做的是将ref赋值给一个非const引用。

加上SadSidos的评论,这使得你的三点不正确。

从这个关于右值引用的Visual c++博客文章:

< p >…c++不希望你意外地 修改临时对象,但是要直接修改 在上调用非const成员函数 可修改的右值是显式的,所以

基本上,你不应该试图修改临时对象,因为它们是临时对象,随时都可能死亡。你被允许调用非const方法的原因是,嗯,欢迎你做一些“愚蠢的”;只要你知道你在做什么,你是明确的(比如,使用reinterpret_cast)。但如果将临时对象绑定到非const引用,则可以“永远”地传递它。让你对对象的操作消失,因为在某个时候你完全忘记了这是临时的。

如果我是你,我会重新考虑我的功能设计。为什么g()接受引用,它修改参数了吗?如果不是,使它const引用,如果是,为什么你试图传递临时给它,你不在乎它是一个临时你正在修改?为什么getx()返回临时的?如果你和我们分享你的真实情况以及你想要实现的目标,你可能会得到一些关于如何做到这一点的好建议。

违背语言和愚弄编译器很少能解决问题——通常它会制造问题。


编辑:在评论中解决问题: 1)' x & x = getx().ref();// OK x什么时候会死?’——我不知道,我也不在乎,因为这正是我所说的“违背语言”。该语言表示“临时对象在语句的末尾死亡,除非它们被绑定为const引用,在这种情况下,当引用超出作用域时它们就会死亡”。应用该规则,x似乎在下一条语句的开头就已经死了,因为它没有绑定到const引用(编译器不知道ref()返回什么)。然而,这只是一个猜测。
  1. 我清楚地说明了目的:不允许修改临时对象,因为这没有意义(忽略c++ 0x右值引用)。问题是“为什么允许我调用非const成员?”是一个很好的答案,但我没有比上面提到的更好的答案了。

  2. 好吧,如果我对X& x = getx().ref();中的x在语句结束时死亡的判断是正确的,那么问题是明显的。

不管怎样,根据你的问题和评论,我认为即使这些额外的答案也不会让你满意。这里是最后的尝试/总结:c++委员会认为修改临时对象没有意义,因此,他们不允许绑定到非const引用。可能是一些编译器实现或历史问题也涉及,我不知道。然后,出现了一些特定的情况,他们决定克服一切困难,仍然允许通过调用非const方法进行直接修改。但这是一个例外——通常不允许修改临时对象。是的,c++经常是那么奇怪。

在你的代码中getx()返回一个临时对象,即所谓的“右值”。你可以把右值复制到对象中(也就是。变量)或将它们绑定到const引用(这将延长它们的生命期,直到引用的生命期结束)。不能将右值绑定到非const引用。

这是一个经过深思熟虑的设计决策,以防止用户意外地修改一个将在表达式结尾死亡的对象:

g(getx()); // g() would modify an object without anyone being able to observe

如果你想这样做,你必须先创建一个对象的本地副本或将其绑定到一个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

右值引用背后的思想是,因为这些对象无论如何都会死亡,你可以利用这一知识并实现所谓的“移动语义”,一种特定的优化:

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

表达式是'getx().ref();',在赋值给'x'之前执行完成。

注意,getx()并不返回引用,而是返回一个进入本地上下文中的完整形式的对象。对象是临时的,但它是 const,因此允许你调用其他方法来计算值或发生其他副作用。

// It would allow things like this.
getPipeline().procInstr(1).procInstr(2).procInstr(3);


// or more commonly
std::cout << getManiplator() << 5;

请看这个答案的最后一个更好的例子

你可以将一个临时对象绑定到一个引用,因为这样做将生成一个对对象的引用,该对象将在表达式的末尾被销毁,从而留给你一个悬空引用(它是不整洁的,标准不喜欢不整洁)。

由ref()返回的值是一个有效的引用,但该方法并不关注它所返回的对象的生命周期(因为它不能在其上下文中拥有该信息)。你所做的基本上相当于:

x& = const_cast<x&>(getX());

对临时对象的const引用可以这样做的原因是,标准将临时对象的生命周期延长到引用的生命周期,因此临时对象的生命周期延长到语句结束之后。

那么剩下的唯一问题是,为什么标准不允许引用临时对象来将对象的生命延长到语句结束之后呢?

我相信这是因为这样做会使编译器很难得到正确的临时对象。这样做是为了对临时对象的const引用,因为它的用途有限,因此迫使你复制对象来做任何有用的事情,但确实提供了一些有限的功能。

想想这种情况:

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

将为您提供易于使用和理解的代码。

如果你想修改值,你应该将值返回给一个变量。如果您试图避免从函数中复制对象的代价(因为看起来对象是复制构造回来的(技术上它是))。那就不用麻烦编译器了,它非常擅长“返回值优化”

邪恶的变通方法涉及'mutable'关键字。实际上,邪恶是留给读者的练习。或者看这里:http://www.ddj.com/cpp/184403758

主要的问题是

g(getx()); //error

是一个逻辑错误:g正在修改getx()的结果,但你没有任何机会检查被修改的对象。如果g不需要修改形参,那么它就不需要左值引用,它可以通过value或const引用获取形参。

const X& x = getx(); // OK

有效,因为有时需要重用表达式的结果,而且很明显您正在处理一个临时对象。

然而,这是不可能的

X& x = getx(); // error

而没有使g(getx())有效,这是语言设计者一开始就试图避免的。

g(getx().ref()); //OK

是有效的,因为方法只知道this的const-ness,它们不知道它们是在左值上调用还是在右值上调用。

就像在c++中一样,你有一个解决这个规则的方法,但是你必须显式地告诉编译器你知道你在做什么:

g(const_cast<x&>(getX()));

问得好,下面是我试图给出的一个更简洁的答案(因为很多有用的信息都在评论中,很难在嘈杂中挖掘出来)。

任何将直接绑定到临时对象的引用都会延长它的生命[12.2.5]。另一方面,由另一个引用初始化的引用将(即使最终是相同的临时引用)。这是有意义的(编译器不知道该引用最终指向什么)。

但这整个想法非常令人困惑。例如,const X &x = X();将使临时值与x引用一样持续,但const X &x = X().ref();将不会(谁知道ref()实际返回什么)。在后一种情况下,X的析构函数在这一行的末尾被调用。(这可以通过非平凡析构函数观察到。)

因此,这通常看起来是令人困惑和危险的(为什么要把关于对象生存期的规则复杂化呢?),但至少需要const引用,所以标准确实为它们设置了这种行为。

[From 印度国家银行 comment]:请注意,将其绑定到const引用增强了a Temporary的生命周期是一个故意添加的异常 (TTBOMK,以便允许手动优化)。没有 为非const引用添加的异常,因为绑定是临时的 对非const的引用被认为最有可能是程序员 错误。< / p >

所有临时变量都将持续到完整表达式结束。然而,要使用它们,你需要一个像ref()一样的技巧。这是合法的。除了提醒程序员正在发生一些不寻常的事情(即,一个引用形参的修改很快就会丢失)之外,似乎没有什么好的理由来跳过这个额外的箍。

[另一个印度国家银行注释]Stroustrup给出的不允许绑定的原因(在D&E中) 对非const引用的右值是,如果Alexey的g()会修改 对象(可以从接受非const参数的函数中得到) 引用),它会修改一个即将死亡的对象,所以没有人 无论如何都可以得到修改后的值。他说,这是最

.

.

似乎关于为什么这是不允许的原始问题已经得到了明确的回答:“因为这很可能是一个错误”。

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

这只是表明可以使用强制转换欺骗编译器。显然,声明并传递一个未使用的自动变量会干净得多:

string name;
string unused;
person.GetNameAndAddr(name, unused); // don't care about addr

这种技术确实在方法的作用域中引入了一个不需要的局部变量。如果出于某种原因,你想防止它在后面的方法中被使用,例如,为了避免混乱或错误,你可以将它隐藏在一个局部块中:

string name;
{
string unused;
person.GetNameAndAddr(name, unused); // don't care about addr
}

——克里斯

我想分享一个场景,我希望我能做到Alexey要求的事情。在一个Maya c++插件中,我必须做以下的恶作剧,以获得一个值到一个节点属性:

MFnDoubleArrayData myArrayData;
MObject myArrayObj = myArrayData.create(myArray);
MPlug myPlug = myNode.findPlug(attributeName);
myPlug.setValue(myArrayObj);

这写起来很乏味,所以我写了以下helper函数:

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

现在我可以把文章开头的代码写成:

(myNode | attributeName) << myArray;

问题是它不能在Visual c++之外编译,因为它试图将从|操作符返回的临时变量绑定到<<操作符。我想它是一个参考,因为这段代码被多次调用,我宁愿没有MPlug被复制这么多。我只需要临时对象存活到第二个函数结束。

这是我的情形。我只是想展示一个人们想做Alexey描述的例子。欢迎大家的批评和建议!

谢谢。

< em > < / em >为什么c++常见问题解答 (黑体我的)中讨论:

在c++中,非const引用可以绑定到左值,const引用可以绑定到左值或右值,但是没有任何东西可以绑定到非const右值。这是防止人们更改临时对象的值,因为临时对象在使用新值之前就被销毁了。例如:

void incr(int& a) { ++a; }
int i = 0;
incr(i);    // i becomes 1
incr(0);    // error: 0 is not an lvalue

如果允许这个incr(0),要么是一些没人见过的临时值被增加,要么更糟——0的值将变成1。后者听起来很愚蠢,但实际上在早期的Fortran编译器中有一个类似的错误,即留出一个内存位置来保存值0。