派生类拷贝赋值运算符中虚基类拷贝赋值运算符的放置位置
虚基类拷贝赋值运算符的放置位置:旧C++与现代标准的差异
90年代老代码注释的原因
90年代的C++编译器对虚继承的实现存在严重缺陷,这是那条注释存在的核心背景:
- 虚基类的子对象在整个继承体系中是唯一的,但当时的编译器生成的默认拷贝赋值,或者开发者手写的直接基类拷贝赋值,经常会错误地调用虚基类的赋值运算符——直接基类的实现没有意识到虚基类的子对象应该由最派生类全权管理。
- 如果派生类先调用虚基类的赋值,再调用直接基类的赋值,直接基类里的虚基类赋值会覆盖掉之前的结果,导致虚基类成员最终是直接基类来源的值,而非最派生类应该传递的正确值,这就是注释里提到的“语义错误”。
举个典型的错误场景:
class VBase { public: int v_val; VBase& operator=(const VBase& rhs) { v_val = rhs.v_val; return *this; } }; class DirectBase : virtual public VBase { public: int d_val; // 90年代常见的手写错误:直接基类手动调用虚基类赋值 DirectBase& operator=(const DirectBase& rhs) { VBase::operator=(rhs); // 此处会修改共享的虚基类成员 d_val = rhs.d_val; return *this; } }; class Derived : public DirectBase { public: int der_val; Derived& operator=(const Derived& rhs) { // 错误顺序:先给虚基类赋值 VBase::operator=(rhs); // v_val被设为rhs的VBase成员值 DirectBase::operator=(rhs); // 此处再次调用VBase::operator=,覆盖v_val为rhs的DirectBase中的VBase值 der_val = rhs.der_val; // 最终v_val并非Derived对象rhs的正确值,出现语义错误 return *this; } };
反过来,先调用DirectBase::operator=,再调用VBase::operator=,最后虚基类的成员会被正确设置为最派生类的rhs值,避免覆盖问题。
现代C的变化(C98及以后)
从C++98标准开始,就明确了虚基类的拷贝赋值责任由最派生类承担,直接基类的拷贝赋值(无论是编译器生成还是手写)都不应操作虚基类的子对象:
- 现代编译器(如GCC 3.x及以后版本)生成的默认拷贝赋值运算符,会自动跳过直接基类中的虚基类子对象,仅处理直接基类自身的非虚成员。
- 手写拷贝赋值的规范写法是:直接基类只负责自身成员的赋值,不调用虚基类的赋值运算符,将虚基类的赋值交给最派生类完成。
在这种规范下,派生类中虚基类赋值的顺序不再是必须的——只要直接基类的拷贝赋值不触碰虚基类,无论先调用哪一个,都不会出现覆盖问题。
结论
那条老注释是针对90年代编译器的虚继承实现缺陷,在现代C++标准和主流编译器(如GCC)中,该问题已被彻底修复。只要遵循以下规范:
- 直接基类的拷贝赋值仅处理自身成员,不调用虚基类的赋值
- 最派生类负责调用所有基类(包括虚基类)的拷贝赋值
就无需刻意关注虚基类赋值的放置顺序。
内容的提问来源于stack exchange,提问作者aackmann
相关产品推荐
相关产品推荐

