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



我正在使用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;
}

不能删除析构函数。如果您希望变量在程序结束前不被销毁,您应该将其声明为static。如果你的类只被用作全局实例或静态实例,我只会这样记录:如果你的库的用户使用它而不带static关键字,他们就错误地使用了你的库。

此外,请注意,如果您的目标是让对象计数器工作,则不必确保在程序结束之前派生类没有被销毁。由于数据成员objects_createdobjects_alive

在链接中的示例中是静态的,因此这些变量将在派生类的第一次实例化之前开始存在,并在程序结束之前停止存在。https://godbolt.org/z/14zMhv993如果你想实现不可能使用派生类作为非静态实例的保护措施,我们可以从单例模式复制。让我们暂时忘记您希望允许类的多个实例并实现单例模式。

#include <iostream>
#include <cassert>
template <typename DERIVED_CLASS, std::size_t size = 0>
class Counter{
protected:
~Counter(){ std::cout << "Counter dtorn"; }
Counter(){ std::cout << "Counter ctorn"; }
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
};

现在每个派生类,例如A,只能有一个实例。Counter::create中的内部静态变量derived只会在第一次调用该函数时实例化一次。

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

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 thisn";
return 0;
}

输出:

Counter ctor
dtor will be called after this
Counter dtor

https://godbolt.org/z/xezKhx1fE

如果你需要一个以上的实例,你可以在create中创建一个静态集合,并在每次函数调用时将/插入到集合中。您必须使用集合,以确保对元素的引用仍然有效。例如,std::unordered_map是正确的,但std::vector不是,因为std::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

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

附录:当然,您可以在堆上创建ACounter实例,但更喜欢smart_pointer:这保证了内存在应该释放的时候被释放,而不需要在程序退出时被操作系统清理。

最新更新