Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keeping track of (stack-allocated) objects

In a rather large application, I want to keep track of some statistics about objects of a certain class. In order to not degrade performance, I want the stats to be updated in a pull-configuration. Hence, I need to have a reference to each live object in some location. Is there an idiomatic way to:

  1. Create, search, iterate such references
  2. Manage it automatically (i.e. remove the reference upon destruction)

I am thinking in terms of a set of smart pointers here, but the memory management would be somewhat inverted: Instead of destroying the object when the smart pointer is destroyed, I'd want the smart pointer to be removed, when the object is destroyed. Ideally, I do not want to reinvent the wheel.

I could live with a delay in the removal of the pointers, I'd just need a way to invalidate them quickly.

edit: Because paddy asked for it: The reason for pull-based collection is that obtaining the information may be relatively costly. Pushing is obviously a clean solution but considered too expensive.

like image 916
choeger Avatar asked Dec 08 '16 08:12

choeger


2 Answers

There is no special feature of the language that will allow you to do this. Sometimes object tracking is handled by rolling your own memory allocator, but this doesn't work easily on the stack.

But if you're using only the stack it actually makes your problem easier, assuming that the objects being tracked are on a single thread. C++ makes special guarantees about the order of construction and destruction on the stack. That is, the destruction order is exactly the reverse of construction order.

And so, you can leverage this to store a single pointer in each object, plus one static pointer to track the most recent one. Now you have an object stack represented as a linked list.

template <typename T>
class Trackable
{
public:
    Trackable()
    : previous( current() )
    {
        current() = this;
    }

    ~Trackable()
    {
        current() = previous;
    }

    // External interface
    static const T *head() const { return dynamic_cast<const T*>( current() ); }
    const T *next() const { return dynamic_cast<const T*>( previous ); }

private:
    static Trackable * & current()
    {
        static Trackable *ptr = nullptr;
        return ptr;
    }

    Trackable *previous;
}

Example:

struct Foo : Trackable<Foo> {};
struct Bar : Trackable<Bar> {};

//  :::

// Walk linked list of Foo objects currently on stack.
for( Foo *foo = Foo::head(); foo; foo = foo->next() )
{
    // Do kung foo
}

Now, admittedly this is a very simplistic solution. In a large application you may have multiple stacks using your objects. You could handle stacks on multiple threads by making current() use thread_local semantics. Although you need some magic to make this work, as head() would need to point at a registry of threads, and that would require synchronization.

You definitely don't want to synchronize all stacks into a single list, because that will kill your program's performance scalability.

As for your pull-requirement, I presume it's a separate thread wanting to walk over the list. You would need a way to synchronize such that all new object construction or destruction is blocked inside Trackable<T> while the list is being iterated. Or similar.

But at least you could take this basic idea and extend it to your needs.

Remember, you can't use this simple list approach if you allocate your objects dynamically. For that you would need a bi-directional list.

like image 109
paddy Avatar answered Sep 20 '22 13:09

paddy


The simplest approach is to have code inside each object so that it registers itself on instantiation and removes itself upon destruction. This code can easily be injected using a CRTP:

template <class T>
struct AutoRef {

    static auto &all() {
        static std::set<T*> theSet;
        return theSet;
    }

private:
    friend T;

    AutoRef() { all().insert(static_cast<T*>(this)); }
    ~AutoRef() { all().erase(static_cast<T*>(this)); }
};

Now a Foo class can inherit from AutoRef<Foo> to have its instances referenced inside AutoRef<Foo>::all().

See it live on Coliru

like image 25
Quentin Avatar answered Sep 21 '22 13:09

Quentin