Search code examples
c++copyc++20destructordeleted-functions

Deriving from a class with a deleted destructor?


I am using CRTP to create a counter class, similar to Object Counter

Additionally, classes that derive from this counter also should not be destructible.

It will look something like this

template <typename DERIVED_CLASS, std::size_t size = 0>
class Counter{
  private:
    ~Counter() = delete;
  protected:
    std::vector<DERIVED_CLASS*> created;
    Counter(){}
  public:
  //More stuff later
};

class A : public Counter<A>{
  public:
    A(){std::cout << "A created" << std::endl;}
    //More stuff later
};

A a;

int main(){
  /* All should be invalid
  A b;
  A c{a};
  A* pA = new A;
  */



  return 0;
}

The creation of a should be the only permitted usage for creating/deleting objects of these types. They should not be copyable, and should last for the entirety of the program life, therefore should not be destructed.

However, I get the the following errors and diagnostic messages

base class 'Counter<A>' has private destructor

destructor of 'A' is implicitly deleted because base class 'Counter<A>' has a deleted destructor

~Counter' has been explicitly marked deleted here

attempt to use a deleted function

Edit:

Just clarifying a few things.

Of course the object will be destructed at program exit. This is desired behaviour. It only should not be destructible by the user or during program life.

Secondly, multiple objects can be created. This is not a create once kind of thing. Many can be created, but not copied or deleted.

Edit: Code for @joerbrech's comment

template <typename DERIVED_CLASS, std::size_t size = 0>
class Counter{
  private:
    Counter(Counter const &) = delete;
    Counter& operator=(Counter const &) = delete;

  protected:
    ~Counter(){}
    Counter(){}

  public:
    template <typename... DERIVED_ARGS>
    static DERIVED_CLASS& create(DERIVED_ARGS... args){
      DERIVED_CLASS* pDerived = new DERIVED_CLASS(args...);
      //Save the pointer into a static collection
      return *pDerived;
    }
  //More stuff later
};


class A: public Counter<A>{
  using Base = Counter<A>;
  friend Base;
  A(int x){}
  //More stuff later
};

A& a = A::create(1);

int main(){
  A& b = A::create(2); //Works (bad)
  // A c(3); //Fails (good)
  // A* pA = new A(4); //Fails (good)

  (void)b; //To silence unused variable warning

  return 0;
}

Solution

  • You cannot delete a destructor. If you want your variable to not be destroyed before the program ends, you should declare it as static. If your class is only meant to every be used as global or static instances, I would just document this: If a user of your library uses it without the static keyword, they use your library wrong.

    In addition, note that if your goal is to have the object counter work, you don't have to make sure that the derived classes aren't destroyed before the program ends. Since the data members objects_created and objects_alive are static in the example in your link, these variables will start to exist before the first instantiation of the derived class and cease to exist no earlier than at the end of your program. https://godbolt.org/z/14zMhv993

    If you want to implement safeguards that make using the derived classes as non-static instances impossible, we can copy from the singleton pattern. Let's forget for a moment that you want to allow more than one instance of your class and implement the singleton pattern.

    #include <iostream>
    #include <cassert>
    
    template <typename DERIVED_CLASS, std::size_t size = 0>
    class Counter{
    
      protected:
        ~Counter(){ std::cout << "Counter dtor\n"; }
        Counter(){ std::cout << "Counter ctor\n"; }
    
      public:
        template <typename... DERIVED_ARGS>
        static DERIVED_CLASS& create(DERIVED_ARGS... args){
          static DERIVED_CLASS derived{args...};
          return derived;
        }
    
        //Counter is non-copyable
        Counter(Counter const &) = delete;
        Counter& operator=(Counter const &) = delete;
    
      //More stuff later
    };
    
    
    class A: public Counter<A>{
      using Base = Counter<A>;
      friend Base;
      A(int x){}
      //More stuff later
    };
    

    Now of every derived class, e.g. A there can only ever be one instance. The inner static variable derived in Counter::create will only be instantiated once in the first call to that function.

    Note also, that the destructor gets called when the program exits:

    int main(){
    
        {
            A& b = A::create(2);
    
            // no new instance will be created at a second call
            assert(&b == &A::create(42));
        }
    
        std::cout << "dtor will be called after this\n";
    
        return 0;
    }
    

    Output:

    Counter ctor
    dtor will be called after this
    Counter dtor
    

    https://godbolt.org/z/xezKhx1fE

    If you need more than one instance, you can create a static collection in create and just emplace/insert into the collection with every function call. You have to use a collection, that makes sure that references to elements remain valid. This is true for std::unordered_map for example, but nor for std::vector, since std::vector has contiguous data storage and might re-allocate.

    #include <unordered_map>
    #include <memory>
    #include <iostream>
    #include <cassert>
    
    template <typename DERIVED_CLASS, std::size_t size = 0>
    class Counter{
    
      protected:
        Counter(){ }
    
      public:
    
        using Map = std::unordered_map<int, DERIVED_CLASS>;
    
        static Map& get_map() {
            static Map map;
            return map;
        }
    
        template <typename... DERIVED_ARGS>
        static DERIVED_CLASS& create(DERIVED_ARGS&&... args){
    
            Map& map = get_map();
            static int idx = 0;
          
            map.insert(
                std::make_pair(
                    idx, 
                    DERIVED_CLASS{std::forward<DERIVED_ARGS>(args)...}
                )
            );
    
            return map.at(idx++);
        }
    
        virtual ~Counter(){ }
    
    
        //Counter is non-copyable
        Counter(Counter const &) = delete;
        Counter& operator=(Counter const &) = delete;
    
        // it must be moveable though to insert into map
        Counter(Counter&&) = default;
        Counter& operator=(Counter&&) = default;
    
      //More stuff later
    };
    
    
    class A: public Counter<A>{
      using Base = Counter<A>;
      friend Base;
      A(int x){}
      //More stuff later
    };
    
    int main(){
    
        A& a = A::create(2);
        A& b = A::create(42);
    
        assert(&a == &A::get_map().at(0));
        assert(&b == &A::get_map().at(1));
        assert(&a != &b);
    
        return 0;
    }
    

    https://godbolt.org/z/qTPdbvsoh

    The destructor of your Counter instances are called in the destructor call of the static map. The static map will be destructed when the program exits. If you want to make sure noone ever tinkers with your static map, you can make get_map private.

    Addendum: Of course you can create A or Counter instances on the heap, but then prefer smart_pointers: This guarantees that the memory is freed when it should and does not need to be cleaned up by the operating system when your program exits.