GoogleMock:跨引用Mock类引发对象泄漏的解决方法
解决Mock对象循环引用导致的内存泄漏问题
问题背景
现有Server与Client两个接口类,各自对应MockServer和MockClient实现。需求是在MockServer中添加clients成员变量,让getClients()方法返回该变量以方便测试时动态修改,但当前实现中,MockServer通过shared_ptr持有MockClient,MockClient又通过shared_ptr持有MockServer,形成循环引用,导致测试结束后Mock对象无法被正常销毁,出现内存泄漏。
原代码示例
#include <gmock/gmock.h> #include <gtest/gtest.h> #include <vector> #include <memory> struct Client; struct Server { virtual const std::vector<std::shared_ptr<Client>>& getClients() = 0; }; struct Client { virtual std::shared_ptr<Server> getServer() = 0; }; struct MockServer : virtual Server { MOCK_METHOD0(getClients, const std::vector<std::shared_ptr<Client>>&()); std::vector<std::shared_ptr<Client>> clients; // 需要保留的变量 protected: MockServer() { ON_CALL(*this, getClients()).WillByDefault(testing::ReturnRef(this->clients)); } ~MockServer() { clients.clear(); std::cout << __func__ << " - This is never printed." << std::endl; } public: static std::shared_ptr<MockServer> create() { return std::make_shared<testing::NiceMock<MockServer>>(); } }; struct MockClient : virtual Client { MOCK_METHOD0(getServer, std::shared_ptr<Server>()); protected: MockClient(std::shared_ptr<Server> server) { ON_CALL(*this, getServer()).WillByDefault(testing::Return(server)); } ~MockClient() { std::cout << __func__ << " - This is never printed." << std::endl; } public: static std::shared_ptr<MockClient> create(std::shared_ptr<Server> server) { return std::make_shared<testing::NiceMock<MockClient>>(server); } }; TEST(ExampleOfLeak, testLeakedMockObjects) { auto server = MockServer::create(); auto client1 = MockClient::create(server); auto client2 = MockClient::create(server); EXPECT_EQ(server, client1->getServer()); EXPECT_EQ(server, client2->getServer()); EXPECT_TRUE(server->getClients().empty()); server->clients.push_back(client1); server->clients.push_back(client2); EXPECT_EQ(client1, server->getClients().front()); EXPECT_EQ(client2, server->getClients().back()); // server->clients.clear(); 手动清理可避免泄漏,但无法在所有测试中手动执行 }
原运行结果
[==========] 运行1个测试用例中的1个测试。 [----------] 全局测试环境初始化。 [----------] ExampleOfLeak中的1个测试 [ RUN ] ExampleOfLeak.testLeakedMockObjects [ OK ] ExampleOfLeak.testLeakedMockObjects (0 毫秒) [----------] ExampleOfLeak中的1个测试(总耗时0毫秒) [----------] 全局测试环境清理 [==========] 1个测试用例中的1个测试执行完毕。(总耗时0毫秒) [ PASSED ] 1个测试。 foo.cc:43: 错误:此Mock对象(用于测试ExampleOfLeak.testLeakedMockObjects)应被销毁但未被销毁。其地址为@0x2fc3170。 foo.cc:43: 错误:此Mock对象(用于测试ExampleOfLeak.testLeakedMockObjects)应被销毁但未被销毁。其地址为@0x2fcbcb0。 foo.cc:22: 错误:此Mock对象(用于测试ExampleOfLeak.testLeakedMockObjects)应被销毁但未被销毁。其地址为@0x2fd6570。 错误:程序退出时发现3个泄漏的Mock对象。
解决方案(不修改原接口及create函数)
核心是打破循环引用:让MockClient内部用weak_ptr保存Server引用,而非shared_ptr,同时保持getServer()返回shared_ptr的接口要求。
修改后的MockClient代码
struct MockClient : virtual Client { MOCK_METHOD0(getServer, std::shared_ptr<Server>()); protected: // 内部用weak_ptr存储Server,避免强引用 std::weak_ptr<Server> server_weak; MockClient(std::shared_ptr<Server> server) : server_weak(std::move(server)) { // 默认行为中把weak_ptr升级为shared_ptr返回,符合原接口 ON_CALL(*this, getServer()).WillByDefault(testing::Invoke([this]() { return server_weak.lock(); })); } ~MockClient() { std::cout << __func__ << " - This is now printed!" << std::endl; } public: static std::shared_ptr<MockClient> create(std::shared_ptr<Server> server) { return std::make_shared<testing::NiceMock<MockClient>>(server); } };
同时调整MockServer的析构(可选,但更稳妥)
可以去掉clients.clear(),因为循环打破后,server销毁时clients里的shared_ptr会自动释放:
~MockServer() { // clients.clear(); 不再需要手动清理 std::cout << __func__ << " - This is now printed!" << std::endl; }
原理说明
MockClient用weak_ptr持有Server,不会增加Server的引用计数,仅保留一个非拥有性的引用。- 当测试结束时,
server的shared_ptr被销毁,其引用计数减为0,MockServer会被正常析构,同时clients里的MockClient的shared_ptr也会被释放,MockClient的引用计数减为0,MockClient也会被正常析构。 - 完全保留了原接口的返回值类型,也没有修改
create函数的签名和逻辑,满足需求。
内容的提问来源于stack exchange,提问作者Fred
相关产品推荐
相关产品推荐

