rtischer8277 rtischer8277 - 15 days ago 5
C++ Question

Removing back pointers with the use of explicit initialization declarations causes std::bad_weak_ptr exception

I have developed some code that compiles correctly, but fails at (debug) runtime. I am using VS2015.

Background: I am building an advanced message engine. To make the programmatic addition of new messages maintainable, in the production code I took the time to craft the initial messages using

explicit initialization declaration
C++ construct. This works and makes the crafting of new messages cookie-cutter, not to mention reducing the maintenance of the messaging guts to almost nothing. Here is the skeleton code for this functionality:

#include <memory>

template< typename D_T >
struct H // prototype for all explicit initialization declarations (EID)
{
H( D_T& d ) : x { d } {}
D_T& x;
};

template< typename D_T >
struct B // base class for derived objects D1 and D2
{
B( D_T& d ) : d { d } {}
D_T& d; // a kind of backptr initialized when the EIDs are contructed

// actual EIDs a and b
H< D_T > a { d };
H< D_T > b { d };
};

struct D1 : public B< D1 >
{
D1() : B( *this ) {}
void Func1() {}
};

struct D2 : public B< D2 >
{
D2() : B( *this ) {}
void Func2() {}
};



int main()
{
D1 d1;
D2 d2;

// as designed either derived object can access either explicitly initialized member a or b
d1.a.x.Func1(); // OK
d1.b.x.Func1(); // OK
d2.a.x.Func2(); // OK
d2.b.x.Func2(); // OK

return 0;
}


This code compiles and runs.

But my derived objects in the real code are shared ptrs. I therefore added this functionality to the code. Notice that I am obtaining the derived class’es
this
ptr using the
enable_shared_from_this
construct:

#include <memory>

template< typename D_T >
struct H
{
H( std::shared_ptr< D_T >& d ) : x { d } {}
std::shared_ptr< D_T >& x;
};

template< typename D_T >
struct B
{
B( std::shared_ptr< D_T >& d ) : d { d } {}
std::shared_ptr< D_T >& d;

H< D_T > a { d }; // a is initialized with D1
H< D_T > b { d };
};

struct D1: public std::enable_shared_from_this< D1 >, public B< D1 >
{
D1() : B( shared_from_this() ) {} // runtime error: bad weak prt
void Func1() {}
};

struct D2: public std::enable_shared_from_this< D2 >, public B< D2 >
{
D2() : B( shared_from_this() ) {}
void Func2() {}
};

int main()
{
D1 d1;
D2 d2;

d1.a.x->Func1();
d1.b.x->Func1();
d2.a.x->Func2();
d2.b.x->Func2();

return 0;
}


This code compiles. However, it does not run and at the D1 constructor, it breaks with exception std::bad_weak_ptr.

I have attempted to change shared ptrs to weak ptrs without success. Anyone see the problem?

Edit 1:
Per @pat’s observation that
shared_from_this()
is not callable from the constructor, see the modified code below which now compiles and runs:

#include <memory>

template< typename D_T >
struct H
{
H( D_T& d ) : x { d } {}
D_T& x;
};

template< typename D_T >
struct B
{
B( D_T& d ) : d { d } {}
D_T& d;

H< D_T > a { d };
H< D_T > b { d };
};

struct D1 : public std::enable_shared_from_this< D1 >, public B< D1 >
{
D1() : B( *this ) {}
void Func1() {}
};

struct D2 : public std::enable_shared_from_this< D1 >, public B< D2 >
{
D2() : B( *this ) {}
void Func2() {}
};

int main()
{
D1 d1;
D2 d2;

d1.a.x.Func1();
d1.b.x.Func1();
d2.a.x.Func2();
d2.b.x.Func2();

return 0;
}

pat pat
Answer

The object has to be managed by a shared pointer for shared_from_this to work. It's actually undefined behavior in C++14 to call shared_from_this on an object which is not already managed by a shared_ptr. So, you won't be able to call shared_from_this from a constructor since the object won't be inside a shared_ptr at that point.

Example from cppreference...

struct Good: std::enable_shared_from_this<Good>
{
    std::shared_ptr<Good> getptr() {
        return shared_from_this();
    }
};
// Bad: shared_from_this is called without having std::shared_ptr owning the caller 
try {
    Good not_so_good;
    std::shared_ptr<Good> gp1 = not_so_good.getptr();
} catch(std::bad_weak_ptr& e) {
    // undefined behavior (until C++17) and std::bad_weak_ptr thrown (since C++17)
    std::cout << e.what() << '\n';    
}
Comments