为什么 T() = T() 是被允许的?

18

我相信表达式 T() 会创建一个 rvalue(根据标准)。然而,下面的代码可以编译通过(至少在 gcc4.0 上):

class T {};

int main()
{
    T() = T();
}

我知道这在技术上是可能的,因为成员函数可以在临时对象上调用,上述代码只是在从第一个 T() 创建的右值临时对象上调用了 operator=。

但是从概念上讲,这就像是将新值赋给了一个右值。这样做允许的原因是什么呢?

编辑:我觉得这很奇怪,因为对于内置类型来说,这是严格禁止的,但对于用户定义的类型来说,却是允许的。例如,int(2) = int(3) 不会编译,因为它是“无效的左值赋值”。

所以,我想真正的问题是,这种有些不一致的行为是否是有意为之的?还是出于某些历史原因而存在?(例如,只允许在 rvalue 表达式上调用 const 成员函数在概念上更合理,但这可能会破坏一些现有的代码。)

5个回答

13

这是因为使用了运算符重载,你可能已经重载了operator=执行一些更加高级的操作,比如打印到控制台,锁定互斥量或者其他任何操作。


17
不仅是 operator=,而且还包括构造函数。在 C++ 中存在许多奇怪的构造方式,编译器只能耸耸肩,希望你知道自己在做什么。 :-) - Owen S.

7
是的,你正在为一个rvalue分配一个新值。更确切地说,你正在调用rvalue上的operator =成员函数。既然你没有使用内置的赋值运算符,为什么你认为这应该是个问题呢?operator =是类的成员函数,它在大多数方面类似于类的任何其他成员函数,包括它可以在rvalues上被调用的事实。
你还应该考虑到“是一个rvalue”的属性是表达式的属性,而不是对象的属性。确实,T()表达式评估为rvalue。尽管如此,T()表达式产生的临时对象仍然是一个对象,也可以作为lvalue访问。例如,可以在赋值结果上调用一些其他成员函数,并通过*this lvalue看到临时对象的“新”(刚刚分配的)值。
(T() = T()).some_member_function();

您还可以通过将const引用附加到它上来延长临时对象的生命周期 const T& r = T() = T();,并且通过r看到的值将是对象的“新”值。正如Johannes在评论中正确指出的那样,这不会将其附加到临时对象上。


是的,我认为我理解了这些操作的机制。但我仍然好奇为什么语言设计者允许r-value(或从r-value表达式创建的临时变量)被这样改变?这是他们的疏忽还是故意允许的(也许有一些实际的原因足以证明内置类型和用户定义类型之间似乎不一致的行为是合理的?) - Rimo
只是为了澄清,个人认为如果只有const成员函数可以在右值表达式上调用,那么这将不会让人感到太惊讶。 - Rimo
3
@Rhimo:这样做会破坏像 Atomic(std::cout) << 1 << " uninterrupted output" << std::endl; 这样的合理结构。 - MSalters
3
这里有一个小错误,我认为:const T& r = T() = T(); 无法工作。你试图将 const 引用绑定到一个左值(由复制赋值运算符返回的 T&),而且随后这个临时变量将被销毁。只有当你尝试将其绑定到引用该临时变量的右值时,生命周期才会延长。 - Johannes Schaub - litb

5
你可以在C++0x中限制operator=仅适用于左值:
class T
{
public:
    T& operator=(const T&) & = default;
};

4
这就是为什么标准库中有几个类可以被实现。例如,考虑 std::bitset<>::operator[]
// bit reference:
class reference {
  friend class bitset;
  reference();
public:
  ˜reference();
  reference& operator=(bool x);           // for b[i] = x;
  reference& operator=(const reference&); // for b[i] = b[j];
  bool operator˜() const; // flips the bit
  operator bool() const;  // for x = b[i];
  reference& flip();      // for b[i].flip();
};

reference operator[](size_t pos); // for b[i];

如果你执行 bits[i] = true,你实际上是将某个类类型的右值分配给了一个值。由operator[]返回的代理可以访问有效地打包到整数中的位。

这个与FredOverflow的答案一起完全解决了我的问题。谢谢! - Rimo

4
从一个角度来看,它是不一致的,但你忽略了它如何保持一致:1)int和其他内置类型仍然像在C中一样工作,2)类类型上的operator=与任何其他方法一样,不需要另一个特殊情况。
自C++诞生以来,C兼容性就受到高度重视,可以说没有C++的C兼容性,C++今天就不会存在。所以这部分通常是好事。
第二点被低估了。不特殊处理operator=允许无意义的代码“工作”,但为什么我们要关心无意义的代码呢?垃圾进,垃圾出。目前的规则给它定义了一个含义(这里的UB会很糟糕),成本可以忽略不计,至少我从来没有见过。
如果按照我的意愿,事情甚至可以更简单,因此int() = int()将被允许。 C++0x开始朝着这个方向前进,使用右值引用,prvalues等。

“C++0x开始朝着这个方向发展,引入了右值引用、纯右值等特性。” 抱抱? - curiousguy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接