240

为什么不允许获取对临时对象的非常量引用,哪个函数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
4

11 回答 11

109

来自这篇关于右值引用的 Visual C++ 博客文章

... C++ 不希望您意外修改临时对象,但直接在可修改的右值上调用非常量成员函数是显式的,因此允许...

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

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

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


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

2)我清楚地说明了目的:不允许修改临时变量,因为它没有意义(忽略 C++0x 右值引用)。问题“那么为什么我可以调用非常量成员?” 是一个很好的答案,但我没有比我上面已经说过的更好的答案了。

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

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

于 2009-10-14T11:57:56.787 回答
42

在您的代码getx()中返回一个临时对象,即所谓的“右值”。您可以将右值复制到对象(也称为变量)中或将它们绑定到 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 引用,并且您可以在“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
于 2009-10-14T13:19:13.657 回答
17

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

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

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

如果要修改值,则应将值返回给变量。如果您试图避免从函数复制对象的成本(因为似乎对象是复制构造回来的(从技术上讲是这样))。那就不要打扰编译器非常擅长'返回值优化'

于 2009-10-14T13:45:27.450 回答
15

为什么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。

于 2015-12-30T10:05:29.647 回答
6

主要问题是

g(getx()); //error

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

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

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

然而,这是不可能的

X& x = getx(); // error

有效而不g(getx())有效,这是语言设计者首先试图避免的。

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

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

与 C++ 中的往常一样,您有一个解决此规则的方法,但您必须通过明确的方式向编译器表明您知道自己在做什么:

g(const_cast<x&>(getX()));
于 2009-11-13T14:39:01.717 回答
5

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

FWIW,我想我会展示如何做到这一点,即使我认为这不是一个好的技术。

我有时想将临时值传递给采用非常量引用的方法的原因是故意丢弃调用方法不关心的按引用返回的值。像这样的东西:

// 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
}

- 克里斯

于 2013-02-22T00:54:45.953 回答
4

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

于 2009-10-14T11:08:09.897 回答
4

邪恶的解决方法涉及“可变”关键字。实际上是邪恶的留给读者作为练习。或在这里查看:http ://www.ddj.com/cpp/184403758

于 2009-10-14T15:02:10.807 回答
4

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

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

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

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

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

所有临时对象都会持续到完整表达式结束。但是,要使用它们,您需要像使用ref(). 那是合法的。似乎没有充分的理由让额外的箍跳过,除了提醒程序员正在发生一些不寻常的事情(即,一个参考参数,其修改将很快丢失)。

[另一个sbi评论] Stroustrup 给出(在 D&E 中)不允许将右值绑定到非 const 引用的原因是,如果 Alexey 的 g() 会修改对象(您希望从一个采用非常量的函数中得到它参考),它会修改一个将要死的对象,所以无论如何没人能得到修改后的值。他说这很可能是一个错误。

于 2012-04-08T19:26:47.493 回答
2

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

在您的示例中,getX() 不返回 const X,因此您可以像调用 X().ref() 一样调用 ref()。您正在返回一个非 const ref,因此可以调用非 const 方法,但您不能将 ref 分配给非 const 引用。

连同 SadSidos 评论,这使您的三点不正确。

于 2009-10-14T11:41:25.820 回答
1

我有一个想分享的场景,我希望我能按照 Alexey 的要求去做。在 Maya C++ 插件中,我必须执行以下恶作剧才能将值放入节点属性中:

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++ 之外编译,因为它试图绑定从 | 返回的临时变量。运算符对 << 运算符的 MPlug 引用。我希望它成为一个参考,因为这段代码被调用了很多次,我不想让 MPlug 被复制这么多。我只需要临时对象生存到第二个函数结束。

好吧,这是我的场景。只是想我会举一个例子,人们想做阿列克谢描述的事情。我欢迎所有的批评和建议!

谢谢。

于 2013-06-22T16:51:40.233 回答