如何在单元测试中访问构造函数内已转移所有权的unique_ptr?
单元测试中访问已转移所有权的
unique_ptr的方案 问题
如何在单元测试中访问已在构造函数内被转移所有权的unique_ptr?曾考虑同时使用shared_ptr与unique_ptr,通过shared_ptr在测试中访问,但担心触发未定义行为(UB)。
初始代码示例
struct Base { virtual int bar() = 0; }; struct B : public Base { MOCK_METHOD(int, bar, (), (override)); }; class A { std::unique_ptr<B> _ptrB; public: A(std::unique_ptr<B> ptr) : _ptrB(std::move(ptr)) { // A接管Utest::ptrB的所有权 } }; struct Utest { Utest() { ptrA = std::make_shared<A>(std::move(ptrB)); } std::shared_ptr<A> ptrA; std::unique_ptr<B> ptrB = std::make_unique<B>(); }; TEST_F(Utest, testA) { // ptrB已被转移,无法访问 EXPECT_CALL(*ptrB, bar()).WillOnce(Return(100)); // 错误:程序异常退出 }
当前解决方案及疑问
通过将Utest设为A的友元类,直接访问A的私有成员_ptrB并向下转型为BaseMock类型用于EXPECT_CALL。但希望能将向下转型步骤移至构造函数,避免在所有单元测试中重复操作,或寻求更优解决方案。
改进后的代码示例
struct Base { virtual int bar() = 0; }; struct BaseMock : public Base { MOCK_METHOD(int, bar, (), (override)); }; class A { std::unique_ptr<BaseMock> _ptrB; friend class Utest; public: A(std::unique_ptr<BaseMock> ptr) : _ptrB(std::move(ptr)) { // A接管Utest::ptrB的所有权 } }; struct Utest { Utest() { ptrA = std::make_shared<A>(std::move(ptrB)); } std::shared_ptr<A> ptrA; std::unique_ptr<BaseMock> ptrB = std::make_unique<BaseMock>(); }; TEST_F(Utest, testA) { BaseMock* ptr = dynamic_cast<BaseMock*>(_ptrB.get()); EXPECT_CALL(*ptr, bar()).WillOnce(Return(100)); }
优化方案
方案1:测试类中提前保留裸指针
在转移unique_ptr所有权之前,先保存对象的裸指针。由于A持有unique_ptr,只要A的生命周期覆盖测试用例,裸指针就始终有效:
struct Utest { Utest() { _mockPtr = ptrB.get(); // 提前保存裸指针 ptrA = std::make_shared<A>(std::move(ptrB)); } std::shared_ptr<A> ptrA; std::unique_ptr<BaseMock> ptrB = std::make_unique<BaseMock>(); BaseMock* _mockPtr; // 供测试用的裸指针 }; TEST_F(Utest, testA) { EXPECT_CALL(*_mockPtr, bar()).WillOnce(Return(100)); }
该方案无需友元或类型转换,实现简单且无额外耦合。
方案2:给A添加测试专用接口(替代友元)
在A中添加仅测试模式下可见的接口,返回BaseMock的指针,避免友元带来的耦合:
class A { std::unique_ptr<BaseMock> _ptrB; public: A(std::unique_ptr<BaseMock> ptr) : _ptrB(std::move(ptr)) {} // 仅测试编译时启用的接口 #ifdef TEST_BUILD BaseMock* getTestMock() { return _ptrB.get(); } #endif }; struct Utest { Utest() { ptrA = std::make_shared<A>(std::move(ptrB)); } std::shared_ptr<A> ptrA; std::unique_ptr<BaseMock> ptrB = std::make_unique<BaseMock>(); }; TEST_F(Utest, testA) { EXPECT_CALL(*ptrA->getTestMock(), bar()).WillOnce(Return(100)); }
方案3:将转型逻辑移至测试类构造函数
利用友元关系,在Utest构造时直接获取A内部的_ptrB并保存,避免每个测试用例重复转换:
struct Utest { Utest() { ptrA = std::make_shared<A>(std::move(ptrB)); _mockPtr = ptrA->_ptrB.get(); // 直接访问A的私有成员 } std::shared_ptr<A> ptrA; std::unique_ptr<BaseMock> ptrB = std::make_unique<BaseMock>(); BaseMock* _mockPtr; }; TEST_F(Utest, testA) { EXPECT_CALL(*_mockPtr, bar()).WillOnce(Return(100)); }
关于混用shared_ptr与unique_ptr的说明
不推荐混用两者:unique_ptr和shared_ptr无法共享对象所有权,若强行将shared_ptr的所有权转移给unique_ptr,shared_ptr会变为空,测试中仍无法访问对象;若让A持有shared_ptr,Utest用weak_ptr观察,则会改变A原本的所有权设计,完全没必要。这种做法要么触发未定义行为,要么达不到测试目的,因此不建议尝试。
内容的提问来源于stack exchange,提问作者xyf
相关产品推荐
相关产品推荐

