0

I am trying to create an abstract class template (InstanceTracker) that classes can inherit from if they need functionality to perform operations on all of their instances. The class holds a static vector of pointers to 'T', and every time an InstanceTracker constructor is run, I push back a new pointer to the vector. I do this through a purely virtual getDerivedPtr() method that returns 'T*', that every class that derives from InstanceTracker has to implement with return this;. You can probably already see what is wrong what this though. You can never call a purely virtual function from a base constructor - since it doesn't exist yet. How can I find a way around this problem for my InstanceTracker class? Here's the code for the class:

#pragma once
#include <vector>

template <typename T>
class InstanceTracker
{
public:
    InstanceTracker() noexcept
    {
        allInstances_.push_back(getDerivedPtr());
    }
    InstanceTracker(const InstanceTracker& source) noexcept
        : InstanceTracker()
    {
    }
    InstanceTracker(const InstanceTracker&& source) noexcept
        : InstanceTracker()
    {
    }
    virtual ~InstanceTracker() noexcept
    {
        auto it = std::find(allInstances_.begin(), allInstances_.end(), this);
        int index = it - allInstances_.begin();
        allInstances_.erase(allInstances_.begin() + index);
    }
    virtual T* getDerivedPtr() = 0;
protected:
    static std::vector<T*> allInstances_;
};

If you want to try to run the code and see why it doesn't work at the moment, here's a simple class that inherits from InstanceTracker:

class Derived1 : public InstanceTracker
{
public:
    Derived1* getDerivedPtr() override
    {
        return this;
    }
};
JensB
  • 839
  • 4
  • 19
  • 1
    Pass a pointer (`this`) to `InstanceTracker`'s ctor? – Stephen Newell Jan 12 '21 at 17:49
  • https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern#Object_counter Looks very similar to the Wikipedia example. https://stackoverflow.com/questions/4173254/what-is-the-curiously-recurring-template-pattern-crtp – puio Jan 12 '21 at 17:51
  • 2
    I would just `static_cast` the `this` pointer in the c'tor. IIRC It's legal to obtain a pointer to an object under construction, so long as the pointer is used in limited ways. Merely storing it is one such way. – StoryTeller - Unslander Monica Jan 12 '21 at 18:00
  • `getDerivedPtr` doesn't need to be virtual. You can just `static_cast(this)` in your base. – super Jan 12 '21 at 18:01

1 Answers1

0

You'd probably be better off using composition rather than inheritance, but I'll assume you have have a good reason to prefer inheritance here.

The difficulty is that, when a base class constructor is run, the this pointer is a pointer to an instance of the base class only. The derived instance doesn't even exit yet. (Likewise, on destruction, the derived portion of the object has already been uninitialized). So if you call a virtual method, you'll get the base class implementation rather than the derived class's implementation. In your case, the base class implementation doesn't even exist, so you're stuck.

You can probably get away with casting the base class's this pointer to a pointer to the derived class, but that's not guaranteed to work and probably involves undefined behavior.

One way to solve this is to store pointers to the base type (InstanceTracker *) rather the pointers to the derived type. Then your getDerivedPtr method doesn't need to be virtual, and it can do the cast when it's safe.

template <typename T>
class InstanceTracker {
    public:
        InstanceTracker() noexcept {
            allInstances_.push_back(this);
        }

        // other constructors elided for space

        virtual ~InstanceTracker() noexcept {
            std::erase(
                std::remove(allInstances_.begin(), allInstances_.end(), 
                            this),
                allInstances.end());
        }

        T* getDerivedPtr() {
            return static_cast<T*>(this);  // downcast
        }

    protected:
        // allInstances_ stores base class pointers
        static std::vector<InstanceTracker*> allInstances_;
};

Notes:

  • If you use RTTI, run-time type identification, you can use dynamic_cast instead of static_cast. You should not use a reinterpret_cast because the compiler might need to adjust the base pointer as part of the cast.

  • You're likely to run into problems if you create an instance of a derived type as const.

Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175
  • 1)The reason storing pointers to InstanceTracker doesn't work for me is because I need to call methods belonging to derived classes on those pointers when I loop over the static vector in derived classes. 2) Why will I run into problems when I declare instances of derived classes as const? Thanks for taking the time to help me by the way – JensB Jan 12 '21 at 20:00