Suppose that we have a simple struct:
struct RefCounters {
size_t strong_cnt;
size_t weak_cnt;
RefCounters() : strong_cnt(0), weak_cnt(0) {}
};
From implementation point, the destructor RefCounters::~RefCounters
should do nothing, since all its members have primitive type. This means that if an object of this type is destroyed with explicit call of destructor (but its memory is not deallocated), then we would be able to work with its members normally after the object is dead.
Now suppose that we have some more classes derived from RefCounters
. Suppose that RefCounters
is present exactly once among base classes of Derived
class. Suppose that destructor is called explicitly for an object of class Derived
, but its memory is not deallocated. Is it OK to access members strong_cnt
and weak_cnt
after that?
From implementation point, it should be OK, at least when there is no virtual inheritance involved. Because Derived*
can be statically cast to RefCounters*
(adding compile-time constant offset to address), and the memory of RefCounters
should not be touched by destructor of Derived
class.
Here is a code sample:
struct RefCounted : public RefCounters {
virtual ~RefCounted() {}
};
struct Base : public RefCounted {
int val1;
virtual void print();
};
struct Derived : public Base {
std::string val2;
virtual void print();
};
Derived *pDer = new Derived();
pDer->~Derived(); //destroy object
pDer->strong_cnt++; //modify its member
std::cout << pDer->strong_cnt << pDer->weak_cnt << "\n";
Is such code considered undefined behavior by C++ standard? Is there any practical reason why it can fail to work? Can it be made legal by minor changes or adding some constraints?
P.S. Supposedly, such code sample allows to make intrusive_ptr + weak_ptr combo, such that weak_ptr can be always obtained from an object pointer if at least one weak_ptr is still pointing at it. More details in this question.