Suppose I have a class X:
struct X
{
...
};
And I have a global vector V:
vector<X*> V;
I want to add a new instance of X to V if and only if it is dynamically allocated (as a complete most derived object, not a subobject):
int main()
{
X x; // not added to V
new X; // added to V
struct D : X {};
new D; // not added to V
}
Is there some way to do this? Perhaps by overloading/overriding operator new
somehow?
struct X {
public:
static void* operator new(std::size_t size) {
void* p = ::operator new(size);
if (size == sizeof(X))
V.push_back(static_cast<X*>(p));
}
static void operator delete(void* p, std::size_t size) {
if (size == sizeof(X))
V.erase(std::remove(V.begin(), V.end(), p), V.end());
::operator delete(p, size);
}
};
Note that there will be times when elements of V
point at memory that is not yet or no longer actually an X
. It's possible for users to get around these functions, but they'd have to be trying.
If you have another class that inherits X
but has the same size (so no other subobjects except maybe "empty" base classes), like struct Y : public X {};
, the code above will think new Y
is allocating an X
. If this is an issue, you'd need to also add operator new
and operator void
to every such class Y
. I don't think there's a more general solution.
Building on aschepler's approach, but now using a virtual base class to redirect D
's constructor calls (to not add an instance to the vector).
Main idea is to do a two-step registration: first, register any call to operator new
(whether it's for X
or a derived class) to an unordered_set
(X::dyn_alloc_set
). Then, when constructing X
, selecting based upon the most-derived type, add this
to V
if it has been dynamically allocated and if it's not a derived class.
The constructor of a virtual base class has to be called from the most-derived type, therefore you can use it to differentiate between D
and X
during construction.
#include <unordered_set>
#include <typeinfo>
#include <vector>
#include <iostream>
#include <algorithm>
struct X;
std::vector<X*> V;
struct virt_base_class
{
friend struct X;
private:
virt_base_class(X* p); // this can only and will only be called by X
public:
virt_base_class() // this will be called by any class derived from X
{}
};
struct X
: protected virtual virt_base_class
{
private:
friend class virt_base_class;
static std::unordered_set<X*> dyn_alloc_set;
static bool dynamically_allocated(X* p)
{
return dyn_alloc_set.count(p) > 0;
}
public:
X()
: virt_base_class(this)
{}
static void* operator new(std::size_t size) {
void* p = ::operator new(size);
if (size == sizeof(X))
dyn_alloc_set.insert( static_cast<X*>(p) );
return p;
}
static void operator delete(void* p, std::size_t size) {
if (size == sizeof(X))
{
dyn_alloc_set.erase( static_cast<X*>(p) );
V.erase( std::remove(V.begin(), V.end(), static_cast<X*>(p)),
V.end() );
}
::operator delete(p);
}
};
virt_base_class::virt_base_class(X* p)
{
if( X::dynamically_allocated(p) )
V.push_back(p);
}
struct D : X
{}; // D::D will implicitly call virt_base_class::virt_base_class()
std::unordered_set<X*> X::dyn_alloc_set;
int main()
{
X x;
X* p = new X;
D d;
D* pd = new D;
std::cout << V.size();
}
Update: using thread_local
storage to avoid the unordered_set
:
struct X
: protected virtual virt_base_class
{
private:
friend class virt_base_class;
static thread_local X* last_dyn_allocated;
static bool dynamically_allocated(X* p)
{
return p == last_dyn_allocated;
}
public:
X()
: virt_base_class(this)
{}
static void* operator new(std::size_t size) {
void* p = ::operator new(size);
if (size == sizeof(X))
{
last_dyn_allocated = static_cast<X*>(p);
}
return p;
}
static void operator delete(void* p, std::size_t size) {
if (size == sizeof(X))
{
X* pp = static_cast<X*>(p);
if(last_dyn_allocated == pp)
last_dyn_allocated = nullptr;
V.erase( std::remove(V.begin(), V.end(), pp),
V.end() );
}
::operator delete(p);
}
};
thread_local X* last_dyn_allocated = nullptr;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With