在不改变派生类成员的情况下赋值基类对象是否合法?
1. 该操作是否合法?
完全合法。你这里的代码是通过基类指针指向派生类对象,然后调用基类的默认赋值运算符(因为Base没有自定义赋值运算符,编译器会自动生成一个)。这个默认赋值运算符的行为是:仅拷贝基类部分的成员变量,派生类特有的成员不会被修改,这是C++标准明确规定的行为。
2. 示例运行符合预期是偶然吗?
不是偶然,是确定的行为。编译器为Base生成的默认赋值运算符Base& Base::operator=(const Base&)只会处理Base类定义内的成员(也就是base_data),Concrete的concrete_data属于派生类独有的成员,不在基类赋值运算符的处理范围内,所以必然会保留原值。
3. 复杂特性下会出现问题吗?
会,当涉及虚函数、动态资源管理等特性时,可能引发未定义行为或不符合预期的结果,常见场景包括:
(1)动态资源管理导致的悬空指针/内存泄漏
如果基类或派生类包含动态分配的指针成员,基类的默认赋值运算符是浅拷贝,会直接拷贝指针的值而非指向的内容。比如:
#include <iostream> struct Base { int* ptr; Base(int x) : ptr(new int(x)) {} ~Base() { delete ptr; } // 默认赋值运算符不会处理资源释放 }; struct Concrete : Base { int* derived_ptr; Concrete(int x, int y) : Base(x), derived_ptr(new int(y)) {} ~Concrete() { delete derived_ptr; } }; int main() { Concrete child(1, 2); Base* parent = &child; *parent = Base(3); // 浅拷贝Base的ptr,覆盖child的ptr // 此时child的ptr指向Base临时对象的内存,临时对象已销毁,ptr变为悬空指针 // 析构child时会delete悬空指针,触发未定义行为 return 0; }
这种情况下,基类赋值操作会破坏派生类对象的资源管理逻辑,导致内存错误。
(2)赋值运算符非虚函数引发的预期不符
赋值运算符默认不是虚函数,如果派生类自定义了赋值运算符,但通过基类指针赋值时,只会调用基类的赋值运算符,不会触发派生类的逻辑。比如:
struct Base { int x; Base& operator=(const Base& other) { x = other.x; return *this; } }; struct Concrete : Base { int y; Concrete& operator=(const Concrete& other) { Base::operator=(other); y = other.y; return *this; } }; int main() { Concrete c1(1,2), c2(3,4); Base* b = &c1; *b = c2; // 仅调用Base的赋值运算符,c1的y仍为2,而非预期的4 return 0; }
如果想让基类指针调用派生类的赋值运算符,需要将基类的赋值运算符声明为虚函数,但这种写法并不常见,且需要注意切片问题。
(3)虚函数行为的混淆
如果基类包含虚函数,赋值基类对象后,派生类对象的实际类型仍为派生类(因为对象的内存布局中的虚表指针未被修改),调用虚函数时仍会执行派生类的实现。比如:
#include <iostream> struct Base { virtual void print() { std::cout << "Base\n"; } int x; }; struct Concrete : Base { void print() override { std::cout << "Concrete\n"; } int y; }; int main() { Concrete c; Base* b = &c; *b = Base(); c.print(); // 输出Concrete,而非Base return 0; }
这可能和部分开发者的直觉不符,误以为赋值基类对象后会改变对象的类型,但实际上只是覆盖了基类部分的成员,对象的本质还是派生类实例。
内容的提问来源于stack exchange,提问作者T.L

