我偶然发现了类似的东西,然后尝试了一些事情并注意到以下内容似乎在G ++中是合法的:
struct A {
int val_;
A() { }
A(int val) : val_(val) { }
const A& operator=(int val) { val_ = val; return *this; }
int get() { return val_; }
};
struct B : public A {
A getA() { return (((A)*this) = 20); } // legal?
};
int main() {
A a = 10;
B b;
A c = b.getA();
}
所以B::getB
返回一个A
类型,然后将20
赋值给自己(通过重载的A::operator=
)。
经过几次测试后,似乎它返回了正确的值(c.get
会返回20
,这可能是人们所期望的。
所以我想知道,这是未定义的行为吗?如果是这样的话,究竟是什么原因呢?如果没有,这样的代码有什么好处?
答案 0 :(得分:3)
经过仔细检查,在@Kerrek SB和@Aaron McDaid的帮助下,以下内容:
return (((A)*this) = 20);
...就像:
的简写(但模糊)语法A a(*this);
return a.operator=(20);
......甚至更好:
return A(*this) = 20;
...因此被定义为行为。
答案 1 :(得分:1)
这里有许多完全不同的事情。代码有效,但您在问题中做出了错误的假设。你说
" B :: getA返回[...],之后将值20分配给本身"
(我的重点)这不正确。 getA 不修改对象。要验证这一点,您只需将const
放在方法签名中即可。然后我会完全解释。
A getA() const {
cout << this << " in getA() now" << endl;
return (((A)*this) = 20);
}
那么这里发生了什么?查看我的sample code(我已将我的成绩单复制到此答案的末尾):
A a = 10;
这声明了一个带有构造函数的A.挺直的。下一行:
B b; b.val_ = 15;
B没有任何构造函数,所以我必须直接写入其val_成员(继承自A)。
在我们考虑下一行A c = b.getA();
之前,我们必须非常仔细地考虑更简单的表达式:
b.getA();
这不会修改b
,虽然它可能看起来像它。
最后,我的示例代码打印出b.val_
,你会看到它仍等于15。它没有变为20. c.val_
当然已改为20。
查看getA
内部,您会看到(((A)*this) = 20)
。让我们打破这个:
this // a pointer to the the variable 'b' in main(). It's of type B*
*this // a reference to 'b'. Of type B&
(A)*this // this copies into a new object of type A.
值得暂停。如果这是(A&)*this
,甚至是*((A*)this)
,那么这将是一个更简单的路线。但它是(A)*this
,因此这会创建一个A类型的新对象,并将相关切片从b复制到其中。
(额外:您可能会问它如何复制切片。我们有B&
引用,我们希望创建一个新的A
。默认情况下,编译器创建一个复制构造函数{{ 1}}。编译器可以使用它,因为引用A :: A (const A&)
可以自然地转换为B&
。)
特别是const A&
。这可能会让你大吃一惊。 (额外:另一方面this != &((A)*this)
通常(取决于是否有this == &((A&)*this)
方法))
现在我们有了这个新对象,我们可以看看
virtual
这会将数字放入此新值中。此语句不影响((A)*this) = 20
。
更改this->val_
使其返回getA
会出错。首先,A&
的返回值为operator=
,因此您无法将其作为const A&
返回。但即使你有A&
作为返回类型,这也是对getA中创建的临时局部变量的引用。回归这些事情是不明确的。
最后,我们可以看到const A&
将采用getA的值返回的此副本
c
这就是为什么当前代码,其中getA按值返回副本,是安全且定义良好的。
==完整的程序==
A c = b.getA();