Search code examples
c++smart-pointers

How do I pass a smart pointer from a function to the caller?


I'm trying to grasp the concept of smart pointers in C++. I have the following piece of code (a unit test using GoogleTest):

TEST(SHT35Sensor, ValidInstruction) {
    auto sht35 = SampleSHT35::create();
    sht35->add(22.4, 56.5);
    char writeBuffer[100] = {0};
    auto serial = std::make_unique<SampleSerial>("", writeBuffer, 0);
    auto sensor = std::make_unique<SHT35Sensor>(0x03, serial.get(), sht35, 0);
    auto actual = sensor->execute(Instruction(0, 0, Bytes("\x02", 1)));
    ASSERT_TRUE(actual);
}

I want to isolate the first five lines of the test in order for them to be reused. I thought that it would be enough (and especially it would be correct) to do this:

std::shared_ptr<SHT35Sensor> prepare() {
    auto sht35 = SampleSHT35::create();
    sht35->add(22.4, 56.5);
    char writeBuffer[100] = {0};
    auto serial = std::make_unique<SampleSerial>("", writeBuffer, 0);
    return std::make_shared<SHT35Sensor>(0x03, serial.get(), sht35, 0);
}

TEST(SHT35Sensor, ValidInstruction) {
    auto sensor = prepare();
    auto actual = sensor->execute(Instruction(0, 0, Bytes("\x02", 1)));
    ASSERT_TRUE(actual);
}

Essentially, I moved the code in a function, and instead of unique_ptr, I used shared_ptr in order to be able to share it between the function which creates it and the caller.

However, the second variant leads to a segmentation fault when running the test, meaning that my understanding of smart pointers is incorrect.

What am I doing wrong?


Solution

  • In your code serial.get() returns pointer, but does not detaches it from unique_ptr, so when prepare ends - unique_ptr deletes SampleSerial instance and shared_ptr contains pointer to freed memory. You may use serial.release() or directly use shared_ptr.

    Above answer assumes that SHT35Sensor will handle lifetime of SampleSerial instance. But if that is not true then pass unique_ptr<SampleErial> to SHT35Sensor:

    return std::make_shared<SHT35Sensor>(0x03, std::move(serial), sht35, 0);
    

    Your SHT35Sensor should accept std::unique_ptr<SampleErial> as second parameter - and pass it to class member using constructor initialization or once again std::move.

    I would prefer the second solution as no bare pointer will be accepted by SHT35Sensor - which is good.