Search code examples
c++constructordestructor

How to prevent the constructor and destructor of derived classes from being called directly in C++


I have an abstract class "Entity". I want to prevent manual calls to the constructor and destructor of its derived classes.

To create and destroy the instances of this class, I use the following function:

template <typename T, typename... Args, typename = std::enable_if_t<!std::is_same_v<Entity, T> && std::is_base_of_v<Entity, T> && std::is_constructible_v<T, Args...>>>
T* SpawnEntity(Args&&... args)
{
    T* instance = new T(std::forward<Args>(args)...);
    
    // ... perform other operations ...
    
    return instance;
}
    
void Destroy(Entity* entity) { ... }

Is it possible to throw an exception or a compile error if the constructor / destructor of any class that extends Entity is called manually?

Edit1: I cannot control all the derived classes of Entity, so I cannot define the constructors as private.

Edit2: My main goal is to prevent other programmers (and myself) from making mistakes. I couldn't find a way to prevent the constructors and destructors from being called directly, but I found a solution where I can print a warning message if the factory class isn't used:

class EntityFactory {
private:
    static inline int s_FactoryUsageDepth = 0;
    friend class Entity;

public:
    template <typename T, typename... Args, typename = std::enable_if_t<!std::is_same_v<Entity, T> && std::is_base_of_v<Entity, T> && std::is_constructible_v<T, Args...>>>
    static T* SpawnEntity(Args&&... args)
    {
        s_FactoryUsageDepth++;

        T* instance = new T(std::forward<Args>(args)...);

        // ... perform other operations ...

        return instance;
    }

    static void Destroy(Entity* entity) 
    {
        s_FactoryUsageDepth++;

        delete entity;

        // ... perform other operations ...
    }
}

class Entity
{
    protected:
        Entity() 
        { 
            if (EntityFactory::s_FactoryUsageDepth-- == 0)
            {
                // ... show an error message ...
            }
        }
        
        virtual ~Entity() 
        {
            if (EntityFactory::s_FactoryUsageDepth-- == 0)
            {
                // ... show an error message ...
            }
        }
}

I use an integer instead of a bool because the constructor of an Entity could call other constructors. I think this solution covers all cases, but let me know if there is something that I'm missing.

Edit3: Small fix in the code


Solution

  • There's very little you can do to prevent other programmers from intentionally circumventing the type system, but you can certainly prevent accidents.

    If you have a "key" user-defined type which has a private constructor (but public copy constructor) and friends your factory function (template), and add a "lock" parameter of this type to each of your constructors, then the derived classes can easily add the "lock" parameter to their constructors and forward them to the base class. Then the derived constructors all also become locked to your factory function.

    Can it be circumvented? Yes, the "lock" will accept the result of std::declval() bypassing the access check on the constructor. But this requires explicit intent.


    Runtime checks can be even more difficult to circumvent. You can have a thread-local flag which is set only inside the factory function, and check that flag from your base class's constructors.


    Yet another option is to give your base class a base class of its own, inherited virtually. Then have your factory function construct not the derived class, but a wrapper that inherits from the user-defined derived class. Use friendship to allow the template wrapper to access the constructor of the virtual base. Doesn't work because defaulted-deleted constructors are turtles all the way down.