构造函数传递引用致运行时异常,继承测试夹具崩溃排查
问题:单元测试夹具继承被测类时构造函数崩溃,组合方式却正常
重构代码后,单元测试出现崩溃:被测代码在项目中运行正常,但在测试夹具中继承该类时,构造函数执行报错。
原始代码
class ClassA : public InterfaceA { public: virtual void SetMember(float member_in) override { member = member_in; } private: float member; }; class ClassB { public: ClassB(InterfaceA& a_in) { a_in.SetMember(6.28f); } }; class ClassBTestFixture : public ClassB { public: ClassBTestFixture() : ClassB(a1) { } private: ClassA a1; }; class ClassBTestFixture2 { public: ClassBTestFixture2() : b(a1) { } private: ClassA a1; ClassB b; }; int main() { ClassBTestFixture fixture; }
运行时异常信息
this->a_in. was 0xCCCCCCCC
但实例化采用组合而非继承的ClassBTestFixture2时却能正常运行,不过这会提升单元测试的编写难度。该问题在VS2019和Linux GCC环境下表现一致。
重构后的代码(已正常运行)
class ClassA { public: virtual void SetMember(float member_in) { member = member_in; } private: float member; }; class ClassB { public: ClassB(ClassA& a_in) : a(a_in) { } void Init() { a.SetMember(6.28f); } private: ClassA& a; }; class ClassBTestFixture : public ClassB { public: ClassBTestFixture() : ClassB(a1) { Init(); } private: ClassA a1; }; int main() { ClassBTestFixture fixture; }
移除了接口(确认并非问题根源),但仍在向基类构造函数传递成员变量,请问这种写法是否合适?
解答
第一个问题的原因:构造函数初始化顺序规则
C++中基类构造函数的执行优先级高于派生类的成员变量初始化:
- 当创建
ClassBTestFixture对象时,会先调用基类ClassB的构造函数,此时派生类的成员a1还未完成构造,传递给ClassB构造函数的a1引用指向的是未初始化的内存(VS中用0xCCCCCCCC标记未初始化的栈内存),调用a_in.SetMember()时自然会触发运行时异常。 - 而
ClassBTestFixture2采用组合方式时,成员变量的初始化顺序严格按照声明顺序执行:a1先于b初始化,所以b的构造函数调用时,a1已经是有效对象,引用合法,因此能正常运行。
重构后写法的合理性分析
你现在的写法是安全且合适的,但需要注意前提条件:
- 基类
ClassB的构造函数仅保存引用,没有立即使用该引用调用成员函数——因为此时a1还未构造。 - 调用
Init()的时机是在派生类的构造函数体中,此时派生类的成员a1已经完成初始化,引用指向的是合法对象,调用a.SetMember()不会有问题。
不过也要留意后续维护的风险:
- 绝对不能在基类
ClassB的构造函数或析构函数中调用Init(),否则会再次触发未初始化对象的访问问题。 - 如果后续修改基类代码时,不小心在构造函数中使用了保存的引用,会重新引入崩溃问题。如果要彻底规避这类风险,也可以考虑在测试时外部传入已初始化的
ClassA对象,比如:
但就当前的实现而言,只要遵循“不在基类构造/析构中使用引用”的规则,现有写法完全可以正常工作。class ClassBTestFixture : public ClassB { public: ClassBTestFixture() : ClassB(a1) { Init(); } private: ClassA a1; };
内容的提问来源于stack exchange,提问作者agmessier
相关产品推荐
相关产品推荐

