Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding C++11 object to list when dynamically allocated?

Tags:

c++

c++11

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?

like image 376
Andrew Tomazos Avatar asked May 29 '13 13:05

Andrew Tomazos


2 Answers

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.

like image 191
aschepler Avatar answered Oct 18 '22 20:10

aschepler


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;
like image 43
dyp Avatar answered Oct 18 '22 21:10

dyp