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

Ale*_*tov 221 c++ const reference 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;
}
Run Code Online (Sandbox Code Playgroud)
  1. 很明显,对象的生命周期不是原因,因为C++标准不禁止对对象的持续引用.
  2. 很明显,上面的示例中的临时对象不是常量,因为允许调用非常量函数.例如,ref()可以修改临时对象.
  3. 此外,ref()允许您欺骗编译器并获取此临时对象的链接,这解决了我们的问题.

此外:

他们说"为const引用分配一个临时对象可以延长这个对象的生命周期","但是对于非const引用却没有任何说法".我的其他问题.以下赋值是否延长了临时对象的生命周期?

X& x = getx().ref(); // OK
Run Code Online (Sandbox Code Playgroud)

sbk*_*sbk 96

这篇关于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& x = getx().ref();在声明结尾处死于x是正确的,问题是显而易见的.

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

  • @sbk:2)实际上,你_are_允许修改rvalues(临时).禁止使用内置类型(`int`等),但允许用户定义的类型:`(std :: string("A")+"B").append("C")`. (5认同)
  • @sbk:3)Stroustrup给出(在D&E中)禁止将rvalues绑定到非const引用的原因是,如果Alexey的`g()`会修改对象(你希望从一个函数中获取非对象) -const reference),它会修改一个将要死的对象,所以无论如何都没有人能够获得修改后的值.他说,这很可能是一个错误. (5认同)
  • 我同意sbi - 这个问题根本不是*挑剔.移动语义的所有基础是类类型rvalues最好保持非const. (5认同)
  • @sbk:1)实际上,正确的短语是:"......在_full表达式的末尾_...".我认为,"完整表达"被定义为不是其他表达式的子表达式.这是否总是与"声明的结尾"相同,我不确定. (2认同)
  • @sbk:如果我冒犯了你,我很抱歉,但我不认为2)是在挑剔.Rvalues根本不是const,除非你这样做并且你可以改变它们,除非它们是内置函数.我花了一段时间来理解,例如,字符串示例,我做错了什么(JFTR:我没有),所以我倾向于认真对待这种区别. (2认同)
  • @sbk:但是能够修改非常量临时值确实有意义.像`f(g()+ = h())`这样的代码不是_that_不常见且不可思议 - 而且这类代码通常依赖于调用非const成员函数的能力.对不起,我仍然觉得你错了.可以修改非POD右值,这是大多数程序员不假思索地认为理所当然的重要特性 - 而不仅仅是在C++ 1x中. (2认同)
  • @sbk,你的语句表明temporaries一直存在到它们包含的语句结尾,这意味着下面的for循环是正确的:`for(char const*s = string().c_str();*s; s ++) ;`.但这是错误的,因为"string()"在语句结束时被破坏*而不是*,但是在初始化`s`结束时,因此使得循环取消引用不归于拥有内存. (2认同)
  • "_C++委员会认为修改临时修改没有意义_"错误. (2认同)

sbi*_*sbi 36

在您的代码中getx()返回一个临时对象,即所谓的"右值".您可以将rvalues复制到对象(也称为变量)或将它们绑定到const引用(这将延长它们的生命周期直到引用生命的结束).您不能将rvalues绑定到非const引用.

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

g(getx()); // g() would modify an object without anyone being able to observe
Run Code Online (Sandbox Code Playgroud)

如果要执行此操作,则必须先制作本地副本或对象,或将其绑定到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
Run Code Online (Sandbox Code Playgroud)

请注意,下一个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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

  • 嗨,这是一个很棒的答案.需要知道一件事,`g(getx())`不起作用,因为它的签名是`g(X&x)`和`get(x)`返回一个临时对象,所以我们不能绑定一个临时对象(**rvalue**)到非常数引用,对吗?在你的第一个代码片段中,我认为它将是`const X&x2 = getx();`而不是`const X&x1 = getx();`.. (2认同)

Mar*_*ork 16

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

 X& x = getx().ref(); // OK
Run Code Online (Sandbox Code Playgroud)

表达式为'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;
Run Code Online (Sandbox Code Playgroud)

看一下这个答案的结尾,以获得更好的例子

不能将临时绑定到引用,因为这样做会生成对将在表达式结尾处销毁的对象的引用,从而留下悬空引用(这是不整洁的,标准不喜欢不整洁).

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

x& = const_cast<x&>(getX());
Run Code Online (Sandbox Code Playgroud)

使用对临时对象的const引用来执行此操作的原因是标准将临时对象的生命周期延长到引用的生命周期,因此临时对象的生命周期延长到语句的结尾之外.

所以唯一剩下的问题是为什么标准不希望允许引用临时数来延长对象的寿命超出语句的结尾?

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

想想这种情况:

int getI() { return 5;}
int x& = getI();

x++; // Note x is an alias to a variable. What variable are you updating.
Run Code Online (Sandbox Code Playgroud)

延长这个临时对象的生命周期将非常混乱.
以下内容:

int const& y = getI();
Run Code Online (Sandbox Code Playgroud)

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

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

  • "因此,唯一剩下的问题是,为什么标准不希望允许临时工具将对象的寿命延长到声明的最后?"**就是这样!**你*理解我的问题.但我不同意你的意见.你说"使编译器非常难",但它是为const引用完成的.你在样本中说"注意x是变量的别名.你要更新什么变量." 没问题.有独特的变量(临时).必须更改一些临时对象(等于5). (3认同)

Ton*_*roy 9

为什么 C++ FAQ( boldfacing mine)中讨论:

在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
Run Code Online (Sandbox Code Playgroud)

如果允许那个incr(0)要么暂时没有人看到会增加或者 - 更糟糕 - 0的值将变为1.后者听起来很愚蠢,但实际上在早期的Fortran编译器中存在类似的错误除了一个内存位置保持值0.

  • 最后一个论点特别站不住脚。没有值得一提的编译器会真正将 0 的值更改为 1,甚至不会以这种方式解释“incr(0);”。显然,如果允许这样做,它将被解释为创建一个临时整数并将其传递给“incr()” (2认同)

Dan*_*dei 6

主要问题是

g(getx()); //error
Run Code Online (Sandbox Code Playgroud)

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

const X& x = getx(); // OK
Run Code Online (Sandbox Code Playgroud)

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

然而,这是不可能的

X& x = getx(); // error
Run Code Online (Sandbox Code Playgroud)

在没有有效的情况下g(getx())有效,这是语言设计者首先想要避免的.

g(getx().ref()); //OK
Run Code Online (Sandbox Code Playgroud)

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

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

g(const_cast<x&>(getX()));
Run Code Online (Sandbox Code Playgroud)


Chr*_*son 5

似乎原来的问题是为什么不允许这一点已被清楚地回答:"因为它很可能是一个错误".

FWIW,我想我会说明如何做到这一点,尽管我认为这不是一个好技巧.

我有时想要将临时文件传递给采用非const引用的方法的原因是故意丢弃由引用返回的值,而调用方法并不关心.像这样的东西:

// Assuming: void Person::GetNameAndAddr(std::string &name, std::string &addr);
string name;
person.GetNameAndAddr(name, string()); // don't care about addr
Run Code Online (Sandbox Code Playgroud)

正如之前的答案中所解释的那样,这不会编译.但这编译并正常工作(使用我的编译器):

person.GetNameAndAddr(name,
    const_cast<string &>(static_cast<const string &>(string())));
Run Code Online (Sandbox Code Playgroud)

这只是表明您可以使用转换来骗取编译器.显然,声明并传递一个未使用的自动变量会更清晰:

string name;
string unused;
person.GetNameAndAddr(name, unused); // don't care about addr
Run Code Online (Sandbox Code Playgroud)

此技术确实将不需要的局部变量引入方法的范围.如果由于某种原因你想阻止它在方法的后期使用,例如,为了避免混淆或错误,你可以将它隐藏在本地块中:

string name;
{
    string unused;
    person.GetNameAndAddr(name, unused); // don't care about addr
}
Run Code Online (Sandbox Code Playgroud)

- 克里斯