类型强制转换的c++赋值

C++ assignment on type cast

本文关键字:c++ 赋值 转换 类型      更新时间:2023-10-16

我今天偶然发现了类似的东西,随后尝试了一些东西,并注意到以下内容在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在将值20赋值给自身(通过重载的A::operator=)之后返回类型A

经过几次测试后,它似乎返回正确的值(c.get将返回20,正如人们所期望的那样)。

所以我想知道,这是未定义行为吗?如果是这样,究竟是什么原因呢?如果不是,这种代码的优点是什么?

经过仔细检查,在@Kerrek SB和@Aaron McDaid的帮助下,如下:

return (((A)*this) = 20);

…类似于

的简写(但模糊)语法:
A a(*this); 
return a.operator=(20);

…或者更好:

return A(*this) = 20;

这里发生了许多完全不同的事情。代码是有效的,但是你在你的问题中做了一个错误的假设。你说

"B::getA返回[…]],然后将值20赋给本身"

(我强调)这是不正确的。getA 修改对象。为了验证这一点,您可以简单地将const放在方法签名中。我会详细解释的。

A getA() const {
    cout << this << " in getA() now" << endl;
    return (((A)*this) = 20);
}

这是怎么回事?看看我的示例代码(我已经复制了我的成绩单到这个答案的末尾):

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。默认情况下,编译器创建一个复制构造函数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&返回。但是,即使您将const A&作为返回类型,这也将是对在getA内部创建的临时局部变量的引用。返回这样的东西是没有定义的。

最后,我们可以看到c将获取getA

返回值的副本
A c = b.getA();

这就是为什么当前的代码,其中getA按值返回副本,是安全和定义良好的。

==完整程序==

#include <iostream>
using namespace std;
struct A {
    int val_;
    A() { }
    A(int val) : val_(val) { }
    const A& operator=(int val) {
        cout << this << " in operator= now" << endl; // prove the operator= happens on a different object (the copy)
        val_ = val;
        return *this;
    }
    int get() { return val_; }
};
struct B : public A {
    A getA() const {
        cout << this << " in getA() now" << endl; // the address of b
        return (((A)*this) = 20);
           // The preceding line does four things:
           // 1. Take the current object, *this
           // 2. Copy a slice of it into a new temporary object of type A
           // 3. Assign 20 to this temporary copy
           // 4. Return this by value
    } // legal? Yes
};
int main() {
    A a = 10;
    B b; b.val_ = 15;
    A c = b.getA();
    cout << b.get() << endl; // expect 15
    cout << c.get() << endl; // expect 20
    B* b2 = &b;
    A a2 = *b2;
    cout << b2->get() << endl; // expect 15
    cout << a2.get() << endl; // expect 15
}