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;
}
Edit 2: The code below is a re-write of my original posting's code and builds on @pat's answer. Here is what was changed: The explicit instantiation declarations (EID) were moved to their derived classes. B is no longer trying to reference the derived object. This was a plain mistake. The weak_ptr as a back pointer was replaced by a simple back ptr (as was the case in the prototypes). No problem with leaks since the derived objects (D1 and D2) own the object outright. (In the producion code the member types are shared ptrs to prevent leaks.)
#include <memory>
#include <cassert>
template< typename D_T >
struct H
{
H( D_T* d ) : x { d } {}
D_T* x;
int qq { 0 };
};
struct B
{
B() {}
int rr { 0 };
};
struct D1 : public B
{
H< D1 > a { this }; // explicit instantiation declaration
int ss { 0 };
};
struct D2 : public B
{
H< D2 > b { this }; // explicit instantiation declaration
int tt { 0 };
};
int main()
{
D1 d1;
D2 d2;
d1.rr = 99;
d2.b.x->rr = 88;
assert( d1.rr == d1.a.x->rr ); // OK
assert( d2.rr == d2.b.x->rr ); // OK
return 0;
}
The design invariant that the code maintenance complexity be reduced from exponential (as was the case in the prototypes) to linear when adding any number of EIDs has been achieved.
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';
}