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
Is the culprit.
However, the real offender is this:
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
Or indeed
Best of all:
Actually consider safe pointer casts (see Using boost::function with a parameter to shared pointer to derived class):
DEMO With Fixes
Live On Coliru
Prints
And no sanitizer warnings.