简体   繁体   中英

Problems with shared_ptr and operator new

I get a bug in a test code, where I have to test a class located at the bottom of a diamond heritage, and above all, which forms a circular dependency with an other class, too difficult to change (I promise it is not my code...).

The constructor of a class A ask for a reference to a class B, and the class B asks for a shared_ptr to the class A...
For unit tests purpose, I'd like to break this dependency, by allocating the memory for A, but without building the instance. Then by passing this shared_ptr to B, and once B is built, by building the A instance whose constructor would take the reference to B.

I succeded to reproduce the problem in a sample code you can execute locally or any online compiler:

#include <iostream>
#include <memory>
using namespace std;

struct ILevel_0
{
    virtual ~ILevel_0() = 0;
};
ILevel_0::~ILevel_0() {}

struct Level_1_A : virtual ILevel_0
{
    int a;
};

struct Level_1_B : virtual ILevel_0
{
    int b;
};

struct Level_2 : Level_1_A, Level_1_B
{
    int c;
};

struct OtherStruct
{
    OtherStruct(const std::shared_ptr<ILevel_0>& lev) : _lev(lev) {}
    std::shared_ptr<ILevel_0> _lev;
};

int main()
{
    std::shared_ptr<Level_2> level2;
    
    void* level2_rawMemory = operator new(sizeof(Level_2));
    Level_2* level2Ptr = static_cast<Level_2*>(level2_rawMemory);
    level2.reset(level2Ptr);
    
    std::cout << "------ 1" << std::endl;
    
    std::shared_ptr<ILevel_0> level0 = level2;  // OK
    
    std::cout << "------ 2" << std::endl;

    // OtherStruct otherStruct(level2);   // KO  : crash after ------ 1
    
    std::cout << "------ 3" << std::endl;

    // Needed, else the shared_ptr's deleter would crash by calling the delete instruction
    new (level2.get()) Level_2{};

    std::cout << "------ 4" << std::endl;
    
    return 0;
}

You can see a line (building an OtherStruct instance) is commented out, so that the code code can be compiled and run wihout crashing. If you uncomment it, the program will crash, without printing "------ 2". I don't understand why, and I don't understand why the previous instruction (level0 building) doesn't crash.

Thanks for help, and sorry for approximative english.

Run your code under ASAN/UBSAN:

在此处输入图像描述

Stepping through reveals that

39          std::shared_ptr<ILevel_0> level0 = level2; // OK

Is the culprit.

However, the real offender is this:

    void* level2_rawMemory = operator new(sizeof(Level_2));
    auto* level2Ptr = static_cast<Level_2*>(level2_rawMemory);
    level2.reset(level2Ptr);

It violates the C++ memory model. You can't just reinterpret raw memory as a Level_2 object. Especially when the data types are not POD (they're not, since they're virtual).

Instead use

    Level_2* level2Ptr = new Level_2;
    level2.reset(level2Ptr);

Or indeed

    level2.reset(new Level_2);

Best of all:

std::shared_ptr<Level_2> level2 = std::make_shared<Level_2>();

Actually consider safe pointer casts (see Using boost::function with a parameter to shared pointer to derived class ):

std::shared_ptr<ILevel_0> level0 = dynamic_pointer_cast<ILevel_0>(level2); // OK

DEMO With Fixes

Live On Coliru

#include <iostream>
#include <memory>
#include <utility>

struct ILevel_0 { virtual ~ILevel_0() = default; };

struct Level_1_A : virtual ILevel_0 { int a{}; };
struct Level_1_B : virtual ILevel_0 { int b{}; };

struct Level_2 : Level_1_A, Level_1_B { int c{}; };

struct OtherStruct {
    explicit OtherStruct(std::shared_ptr<ILevel_0> lev) : _lev(std::move(lev)) {}
    std::shared_ptr<ILevel_0> _lev;
};

int main() {
    std::cout << std::unitbuf;
    std::shared_ptr<Level_2> level2 = std::make_shared<Level_2>();

    std::cout << "------ 1" << std::endl;
    auto level0 = std::dynamic_pointer_cast<ILevel_0>(level2); // OK

    std::cout << "------ 2" << std::endl;

    OtherStruct otherStruct(level2);

    std::cout << "------ 3" << std::endl;

    level2 = std::make_shared<Level_2>();

    std::cout << "------ 4" << std::endl;
}

Prints

------ 1
------ 2
------ 3
------ 4

And no sanitizer warnings.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM