Search code examples
c++pointersgoogletestgooglemock

Smart pointer gets prematurely deleted due to GTest teardown


I am currently refactoring codes to make raw pointers use smart pointers and also making test for this class but stuck with an issue of a smart pointer being prematurely deleted

Here is an example:

class SomeObjectType
{
public:
   void init()
   {
   }
};

class Helper
{
public:
    Helper()
    {
        std::cout << "Helper constructor" << std::endl;
       //some codes
    }
    ~Helper()
    {
        std::cout << "Helper destructor" << std::endl;
       //some codes
    }
    SomeObjectType* createObject()
    {
        return new SomeObjectType;
      //some codes
    }
    void destroyObject(SomeObjectType* obj)
    {
       //some codes
    }
};

class Base
{
public:
    Base()
    {
       helper = std::make_shared<Helper>();
    }
    ~Base()
    {
       destroyObject(obj);
    }
    void Init()
    {
       obj = createObject();
    }
    SomeObjectType* createObject()
    {
       return helper->createObject();
    }
    void destroyObject(SomeObjectType* obj)
    {
       helper->destroyObject(obj); // <-- I get the error here
    }

protected:
    std::shared_ptr<Helper> helper;

private:
    SomeObjectType* obj;

FRIEND_TEST(BaseTest , Init_handleSuccess);
};

And in my test:

class BaseTest : public ::testing::Test
{
public:
    void SetUp()
    {
        sut_ = std::make_unique<Base>();
        helperMock_ = std::make_shared<HelperMock>();
        //helperMock_ = new HelperMock;  //it works when I dont use smart pointers but of course there is a leak here
    }

protected:
    std::unique_ptr<Base> sut_;
    std::shared_ptr<HelperMock> helperMock_;
    //HelperMock* helperMock_; //it works when I dont use smart pointers but of course there is a leak here
};

TEST_F(BaseTest, Init_handleSuccess)
{
    auto obj = new SomeObjectType();
    sut_->helper.reset(helperMock_.get());
    EXPECT_EQ(S_OK, sut_->Init());
}

When I checked the logs, I see that this is what happens (in this order):

  • Test setup called

  • Base constructor called

  • Helper constructor called

  • Test teardown

  • Helper destructor called

  • Base destructor called

  • Base destructor tries to access helper which was already deleted = crash!

So basically helper pointer was already deleted prior to call of destroyObject() because its life was tied with my test. So when I change the test to use a raw pointer for helper, I do not get the double delete but then it becomes a leak since I didnt delete helper at all :) What can I do so that helper is still alive when the base class destructor is called?

Note: There is some API calls I need inside the helper's createObject that is why I am mocking it. I can't inject helper as a dependency that is why I have to 'hack' it with sut_->helper.reset(helperMock_.get());


Solution

  • I believe this might be your issue...

    sut_->helper.reset(helperMock_);
    

    I'm not even sure how that code above could possibly be compiling, because I thought that reset expects a raw pointer for the shared_ptr to take ownership of. (And as you updated in the comments: you were really invoking reset with .get() ).

    And the biggest mistake you can make with a shared_ptr is to invoke .get() or use * operator - and pass the returned raw pointer to initialize another shared_ptr. Now you got two different reference counting sessions on the same object. Which ever set of shared_ptr instances goes away first will delete the object. Leaving the other set of shared_ptrs referencing an already deleted object.

    Replace the above with just this:

    sut_->helper = helperMock_;
    

    I'm assuming HelperMock derives from Helper.