我正在使用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_created
和objects_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
设为私有。
附录:当然,您可以在堆上创建A
或Counter
实例,但更喜欢smart_pointer:这保证了内存在应该释放的时候被释放,而不需要在程序退出时被操作系统清理。