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

时间:2009-10-14 11:01:22

标签: c++ reference const temporary c++-faq

为什么不允许对临时对象进行非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;
}
  1. 很明显,对象的生命周期不能成为原因,因为 C ++标准对未禁止的对象的持续引用。
  2. 很明显,上面的示例中的临时对象不是常量,因为允许调用非常量函数。例如,ref()可以修改临时对象。
  3. 此外,ref()允许您欺骗编译器并获取此临时对象的链接,这可以解决我们的问题。
  4. 此外:

    他们说“为const引用分配一个临时对象可以延长这个对象的生命周期”和“尽管如此,对于非const引用没有任何说法”。 我的其他问题。以下赋值是否会延长临时对象的生命周期?

    X& x = getx().ref(); // OK
    

11 个答案:

答案 0 :(得分:91)

从此Visual C++ blog article about rvalue references

  

... C ++不会让你意外   修改临时,但直接   调用非const成员函数   可修改的右值是明确的,所以   这是允许的......

基本上,您不应该尝试修改临时对象,因为它们是临时对象并且现在会在任何时刻死亡。你被允许调用非const方法的原因是,欢迎你做一些“愚蠢”的事情,只要你知道你在做什么,你明确它(比如,使用reinterpret_cast)。但是如果你将一个临时引用绑定到一个非const引用,你可以继续“永远”传递它,只是为了让你对对象的操作消失,因为在你完全忘记这个过程的某个地方是暂时的。

如果我是你,我会重新考虑我的功能设计。为什么g()接受引用,是否修改参数?如果不是,请将其作为const引用,如果是,为什么要尝试将临时文件传递给它,您不关心它是否是您正在修改的临时文件?为什么getx()还是暂时返回?如果您与我们分享您的真实场景以及您想要完成的任务,您可能会得到一些关于如何做的好建议。

违背语言并愚弄编译器很少能解决问题 - 通常会产生问题。

<小时/> 编辑:在评论中解决问题: 1)X& x = getx().ref(); // OK when will x die? - 我不知道而且我不在乎,因为这正是我所说的“反对语言”。语言说“临时语句在语句结束时死亡,除非它们被const引用绑定,在这种情况下,当引用超出范围时它们就会死亡”。应用该规则,似乎x在下一个语句的开头已经死了,因为它没有绑定到const引用(编译器不知道ref()返回什么)。但这只是猜测。

2)我明确说明了目的:你不能修改临时工,因为它没有意义(忽略C ++ 0x右值引用)。问题是“那为什么我可以打电话给非常规会员?”是一个很好的,但我没有比上面已经说过的更好的答案。

3)好吧,如果我在声明结尾处X& x = getx().ref();死亡时x是正确的,那么问题就很明显了。

无论如何,根据你的问题和评论,我不认为即使这些额外的答案也能满足你。这是最后的尝试/总结:C ++委员会认为修改临时数没有意义,因此,他们不允许绑定到非const引用。可能是某些编译器实现或历史问题也涉及,我不知道。然后,出现了一些具体案例,并且决定不顾一切,他们仍然允许通过调用非const方法进行直接修改。但这是一个例外 - 通常不允许您修改临时对象。是的,C ++通常很奇怪。

答案 1 :(得分:33)

在您的代码getx()中返回一个临时对象,即所谓的“右值”。您可以将rvalues复制到对象(也称为变量)或将它们绑定到const引用(这将延长它们的生命周期直到引用生命的结束)。您不能将rvalues绑定到非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 ++标准将包含右值引用。因此,您所知的参考文献将被称为“左值参考”。您将被允许将rvalues绑定到右值引用,您可以在“rvalue-ness”上重载函数:

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

答案 2 :(得分:16)

您所展示的是允许操作员链接。

 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;

Look at the end of this answer for a better example of this

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

ref()返回的值是一个有效的引用,但该方法不会关注它返回的对象的生命周期(因为它不能在其上下文中包含该信息)。你基本上只完成了相同的:

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

使用对临时对象的const引用来执行此操作的原因是标准将临时对象的生命周期延长到引用的生命周期,因此临时对象的生命周期延长到语句结束之后。< / p>

所以唯一剩下的问题是为什么标准不希望允许引用临时对象将对象的生命延长到语句结束之外?

我相信这是因为这样做会使编译器很难对临时对象进行更正。它是针对temporaries的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();

将为您提供直观易用的代码。

如果要修改该值,则应将该值返回给变量。如果你试图避免从函数中复制obejct的成本(因为它似乎是复制构造的对象(技术上是))。然后不要打扰编译器非常擅长'Return Value Optimization'

答案 3 :(得分:9)

为什么 C++ FAQ boldfacing 我的)中讨论过:

  

在C ++中,非const引用可以绑定到lvalues,const引用可以绑定到lvalues或rvalues,但是没有任何东西可以绑定到非const rvalue。那就是来保护人们不要在他们的新价值被使用之前改变被摧毁的临时价值。例如:

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的编译器。

答案 4 :(得分:6)

主要问题是

g(getx()); //error

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

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

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

然而,无法制作

X& x = getx(); // error

有效且不会使g(getx())有效,这是语言设计者首先想要避免的。

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

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

与C ++一样,你有一个解决这个规则的方法,但你必须通过明确告诉编译器你知道你正在做什么:

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

答案 5 :(得分:5)

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

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
}

- 克里斯

答案 6 :(得分:4)

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

答案 7 :(得分:4)

邪恶的解决方法涉及'mutable'关键字。实际上,邪恶是留给读者的练习。或者点击此处:http://www.ddj.com/cpp/184403758

答案 8 :(得分:3)

很好的问题,这是我尝试更简洁的答案(因为很多有用的信息都在评论中,很难在噪音中挖掘出来。)

直接直接绑定到临时的引用将延长其寿命[12.2.5]。另一方面,用另一个引用初始化的引用将(即使它最终是相同的临时)。这是有道理的(编译器不知道该引用最终指的是什么)。

但这整个想法非常令人困惑。例如。只要const X &x = X();引用x const X &x = X().ref();ref()将不会(X知道ref()实际返回的内容)。在后一种情况下,{{1}}的析构函数在此行的末尾被调用。 (这可以通过非平凡的析构函数来观察。)

所以它似乎通常令人困惑和危险(为什么复杂的关于对象生存期的规则?),但可能至少需要const引用,所以标准确实为它们设置了这种行为。

  

[来自sbi评论]:请注意,将其绑定到const引用的事实增强了   临时的生命周期是故意添加的例外   (TTBOMK以便允许手动优化)。没有   为非const引用添加了异常,因为绑定了一个临时的   很多人认为非const引用很可能是一个程序员   错误。

所有临时表都会持续到完整表达结束。但是,要使用它们,您需要一个与{{1}}一样的技巧。那是合法的。除了提醒程序员发生了一些不寻常的事情(即一个参数参数的修改很快就会丢失)之外,似乎没有充分的理由让额外的箍跳过去。

  

[另一个sbi评论] Stroustrup给出(在D&amp; E中)禁止绑定的原因   非const引用的rvalues,如果Alexey的g()将修改   对象(你期望从一个非const的函数   参考),它会修改一个将要死的对象,所以没有人   无论如何都可以获得修改后的价值。他说这个,大多数   可能,是一个错误。

答案 9 :(得分:2)

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

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

与SadSidos一起评论这会使你的三点不正确。

答案 10 :(得分:1)

我有一个场景我想分享,我希望我可以做到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);
}

现在我可以从帖子的开头写下代码:

(myNode | attributeName) << myArray;

问题是它不能在Visual C ++之外编译,因为它试图绑定从|返回的临时变量。运算符到&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;运营商。我希望它是一个参考,因为这个代码被多次调用,我宁愿没有复制MPlug这么多。我只需要临时对象直到第二个函数结束。

嗯,这是我的情景。只是想我会展示一个人们想要做Alexey描述的例子。我欢迎所有批评和建议!

感谢。