题 为什么非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;
}
  1. 很明显,物体的寿命不是原因,因为 对对象的常量引用是 不禁止 通过C ++标准。
  2. 很明显,上面的示例中的临时对象不是常量,因为允许调用非常量函数。例如, ref() 可以修改临时对象。
  3. 此外, ref() 允许你欺骗编译器并获得这个临时对象的链接,这解决了我们的问题。

此外:

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

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

189
2017-10-14 11:01


起源


我不同意“对象的生命周期不能成为原因”部分,只是因为在标准中声明,将临时对象分配给const引用会将此对象的生命周期延长到const引用的生命周期。关于非const引用,没有任何说法...... - SadSido
那么,究竟是什么原因“虽然......但没有关于非常规引用的说法”。这是我提问的一部分。这有什么意义吗?可能是Standard的作者只是忘记了非const引用,很快我们就会看到下一个Core Issue? - Alexey Malistov
GotW#88:“最重要的const”的候选人。 herbsutter.spaces.live.com/blog/cns!2D4327CC297151BB!378.entry - fnieto - Fernando Nieto
@Michael:VC将rvalues绑定到非const引用。他们称这是一个功能,但实际上这是一个错误。 (请注意,这不是一个错误,因为它本质上是不合逻辑的,但因为它被明确排除以防止愚蠢的错误。) - sbi
Herb Sutter's GotW#88 :“最重要的const”的候选人 已出动。 - sarnold


答案:


由此 关于rvalue引用的Visual C ++博客文章

... 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 rvalue引用)。问题是“那为什么我可以给非常规成员打电话?”是一个很好的,但我没有比上面已经说过的更好的答案。

3)好吧,如果我对x in是正确的 X& x = getx().ref(); 在声明结束时死亡,问题是显而易见的。

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


83
2017-10-14 11:57



@sbk:1)实际上,正确的短语是:“......在...的最后 充分表达......“。我相信,”完整表达“被定义为不是其他表达式的子表达式。这是否总是与”声明的结尾“相同,我不确定。 - sbi
@sbk:2)实际上,你 是 允许修改rvalues(临时)。禁止使用内置类型(int 等),但允许用户定义的类型: (std::string("A")+"B").append("C")。 - sbi
@sbk:3)Stroustrup给出(在D&E中)禁止将rvalues绑定到非const引用的原因是,如果Alexey的 g() 会修改对象(你期望从一个非const引用的函数),它会修改一个将要死的对象,所以没有人可以得到修改后的值。他说,这很可能是一个错误。 - sbi
@sbk:如果我冒犯了你,我很抱歉,但我不认为2)是在挑剔。 Rvalues根本不是const,除非你这样做并且你可以改变它们,除非它们是内置函数。我花了一段时间来理解,例如,字符串示例,我做错了什么(JFTR:我没有),所以我倾向于认真对待这种区别。 - sbi
我同意sbi - 这件事是 一点也不 吹毛求疵。移动语义的所有基础是类类型rvalues最好保持非const。 - Johannes Schaub - litb


在你的代码中 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

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

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

29
2017-10-14 13:19



嗨,这是一个很棒的答案。需要知道一件事, g(getx()) 不起作用,因为它的签名是 g(X& x) 和 get(x) 返回一个临时对象,所以我们不能绑定一个临时对象(右值)到非常量引用,对吗?在你的第一个代码中,我认为它会是 const X& x2 = getx(); 代替 const X& x1 = getx();.. - SexyBeast
感谢您在我写完这篇文章5年后的答案中指出这个错误! :-/ 是的,你的推理是正确的,虽然有点倒退:我们不能将临时工具绑定到非const (左值)引用,因此返回的临时值 getx() (并不是 get(x))不能绑定到参数的左值引用 g()。 - sbi
嗯,你是什么意思 getx() (并不是 get(x))? - SexyBeast
当我写作 “... getx()(而不是get(x))......”,我的意思是函数的名字是 getx(), 并不是 get(x) (正如你所写)。 - sbi
哦,是的,当然。谢谢.. :) - SexyBeast


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

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

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

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


16
2017-10-14 13:45



“因此,唯一剩下的问题是,为什么标准不希望允许参考临时工具将对象的寿命延长到声明的最后?” 这就对了! 您 理解 我的问题。但我不同意你的意见。你说“使编译器非常难”,但它是为const引用完成的。你在样本中说“注意x是变量的别名。你要更新什么变量。”没问题。有独特的变量(临时)。必须更改一些临时对象(等于5)。 - Alexey Malistov
@Martin:悬空参考不仅不整洁。当稍后在方法中访问时,它们可能导致严重的错误! - mmmmmmmm
@Alexey:请注意,将它绑定到const引用的事实增强了临时的生命周期 例外 故意添加(TTBOMK以允许手动优化)。没有为非const引用添加异常,因为看起来将临时绑定绑定到非const引用很可能是程序员错误。 - sbi
@rstevens:那是我的观点;-) - Martin York
@alexy:对可变变量的引用!不那么直观。 - Martin York


为什么 在讨论中 C ++ FAQ (加粗 矿):

在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。


7
2017-12-30 10:05



看到被Fortran“零虫”咬伤的程序员的脸,本来很有趣! x * 0  给 x?什么?什么?? - John D
最后一个论点特别薄弱。没有值得一提的编译器实际上会将0的值更改为1,甚至解释 incr(0); 以这样的方式。显然,如果允许这样做,它将被解释为创建一个临时整数并将其传递给 incr() - user3204459


主要问题是

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

6
2017-11-13 14:39





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

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
}

- 克里斯


5
2018-02-22 00:54





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


4
2017-10-14 11:08



Becouse我想打电话 g(getx()) 而不是 g(getx().ref()) - Alexey Malistov
@Alexey,这不是一个真正的原因。如果你这样做,那么你有一个逻辑错误soemwhere,因为 g 将修改一些你无法掌握的东西。 - Johannes Schaub - litb
@ JohannesSchaub-litb也许他不在乎。 - curiousguy
“依靠RVO“除了它不叫”RVO“。 - curiousguy
@curiousguy:那是一个 非常 接受的条款。将它称为“RVO”绝对没有错。 - Puppy


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


4
2017-10-14 15:02





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

任何参考绑定  暂时的延长寿命[12.2.5]。另一方面,用另一个引用初始化的引用将  (即使它最终是暂时的)。这是有道理的(编译器不知道该引用最终指的是什么)。

但这整个想法非常令人困惑。例如。 const X &x = X(); 会使临时最后一次 x 参考,但是 const X &x = X().ref(); 不会(谁知道什么 ref() 实际上回来了)。在后一种情况下,析构函数为 X 在这一行的末尾被调用。 (这可以用非平凡的析构函数观察到。)

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

[从 SBI 评论]:请注意,将它绑定到const引用的事实增强了   临时的生命周期是一个特意,是故意添加的   (TTBOMK以便允许手动优化)。没有   为非const引用添加了异常,因为绑定了一个临时的   对非const引用的看法很可能是一个程序员   错误。

所有的临时表都会持续到完整表达结束。然而,要使用它们,你需要一个像你一样的技巧 ref()。这是合法的。除了提醒程序员发生了一些不寻常的事情(即,其修改将很快丢失的参考参数)之外,似乎没有充分的理由让额外的环跳过。

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


3
2018-04-08 19:26





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

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

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


2
2017-10-14 11:41