从具有已删除析构函数的类派生?

Deriving from a class with a deleted destructor?

提问人:Nathan29006781 提问时间:12/13/2022 最后编辑:Nathan29006781 更新时间:12/15/2022 访问量:166

问:

我正在使用 CRTP 创建一个计数器类,类似于对象计数器

此外,从此计数器派生的类也不应是可破坏的。

它看起来像这样

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;
}

创建应该是创建/删除这些类型的对象的唯一允许用法。它们不应该是可复制的,并且应该在整个程序生命周期内持续存在,因此不应该被破坏。a

但是,我收到以下错误和诊断消息

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

编辑:

只是澄清一些事情。

当然,该对象将在程序退出时被销毁。这是期望的行为。它不应该被用户或在程序生命周期内破坏。

其次,可以创建多个对象。这不是一次创造的事情。许多可以创建,但不能复制或删除。

编辑:@joerbrech评论的代码

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;
}
C++ 复制 C++20 析构函数 deleted-functions

评论

1赞 Some programmer dude 12/13/2022
当程序结束时,在函数返回后,全局对象将被析构。这就是C++的工作原理。maina
2赞 Remy Lebeau 12/13/2022
仅仅因为在全局范围内声明并不能阻止它在程序退出时被销毁。特别是因为它有一个具有析构函数的数据成员。所以,你的例子没有意义。如果你真的想阻止创建/破坏,那么就做一个带有(不是)构造函数和析构函数的单例类,然后通过你希望创建它的工厂创建单个实例。aAprivatedeletedpublic
1赞 Some programmer dude 12/13/2022
也许您正在寻找的是某种单例,或者某种类似的“创建一次”模式?
2赞 Peter 12/13/2022
你不需要 的析构函数,也不需要制作它。然后,当程序正常退出时,该对象将被销毁。在此之前,它不会被摧毁。如果程序员选择在此之前显式销毁它,那么程序员将对任何后果负责(例如,如果对象第二次被销毁,则未定义的行为)。套用Bjarne Stroustrup的话来说,你试图阻止马基雅维利程序员的行为——而C++功能是防止墨菲而不是马基雅维利出现问题的工具。deleteCounterprivate
1赞 joergbrech 12/13/2022
你不就是想创建一个静态变量吗?如果你迫切地想要强制所有实例都是静态的,你可以将构造函数设为私有,删除复制并移动 ctor,并将友元函数或静态成员函数作为“工厂”(需要更好的术语),将新实例添加到合适的静态集合中,并返回对所创建元素的引用/指针/迭代器。

答:

1赞 joergbrech 12/14/2022 #1

您无法删除析构函数。如果希望变量在程序结束前不被销毁,则应将其声明为 。如果你的类只意味着每个都用作全局或静态实例,我会记录以下内容:如果你的库的用户在没有 static 关键字的情况下使用它,他们就会错误地使用你的库。static

此外,请注意,如果目标是让对象计数器工作,则不必确保在程序结束之前不会销毁派生类。由于数据成员 和 在链接中的示例中是静态的,因此这些变量将在派生类的第一次实例化之前开始存在,并且不早于程序结束时停止存在。https://godbolt.org/z/14zMhv993objects_createdobjects_alive

如果要实现保护措施,使得无法将派生类用作非静态实例,我们可以从单例模式中复制。让我们暂时忘记,您希望允许类的多个实例并实现单例模式。

#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
};

现在每个派生类,例如 永远只能有一个实例。内部静态变量 in 只会在第一次调用该函数时实例化一次。AderivedCounter::create

另请注意,析构函数在程序退出时被调用:

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;
}

输出:

Counter ctor
dtor will be called after this
Counter dtor

https://godbolt.org/z/xezKhx1fE

如果需要多个实例,则可以在 中创建一个静态集合,并在每次函数调用时将该集合放置/插入到集合中。您必须使用一个集合,以确保对元素的引用保持有效。例如,这是正确的,但对于 也是如此,因为具有连续的数据存储并且可能会重新分配。createstd::unordered_mapstd::vectorstd::vector

#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

实例的析构函数在静态映射的析构函数调用中调用。当程序退出时,静态映射将被破坏。如果你想确保没有人修改你的静态地图,你可以设为私有。Counterget_map

附录:当然,您可以在堆上创建 或 实例,但最好smart_pointers: 这保证了内存在程序退出时操作系统应该释放和不需要清理时释放内存。ACounter