使用c++11实现泛型单例的思考
1、使用模板实现一个单例模式,如下
#include <iostream>
#include <memory>
#include <mutex>
template<typename T>
class Singleton {
public:
static T& instance() {
static std::once_flag flag;
std::call_once(flag, []() {
m_instance.reset(new T);
});
return *m_instance.get();
}
protected:
Singleton() = default;
virtual ~Singleton() = default;
private:
static std::unique_ptr<T> m_instance;
};
template<typename T>
std::unique_ptr<T> Singleton<T>::m_instance;
class MyClass : public Singleton<MyClass> {
public:
void print() {
std::cout << "Hello World!" << std::endl;
}
};
int main() {
MyClass::instance().print();
}
这个示例代码中,我们定义了一个Singleton类模板,它有一个instance方法,用于获取单例对象。在instance方法中,我们使用了std::call_once和std::once_flag来保证线程安全。同时,我们使用了std::unique_ptr来管理单例对象的生命周期。
2、析构函数设置为virtual有什么好处和坏处
析构函数设置为virtual的好处是,当使用基类指针指向子类对象时,如果基类的析构函数不是虚函数,那么只会调用基类的析构函数,而不会调用子类的析构函数。这样就会导致子类中的资源无法被释放,从而导致内存泄漏。
因此,当定义一个基类时,如果它有可能被用作多态类型,那么就应该将其析构函数声明为virtual。这样,在使用基类指针指向子类对象时,就可以正确地调用子类的析构函数了。
但是,析构函数设置为virtual也有一些坏处。例如,含有virtual的函数其对象的体积会增加,因为它多了一个vptr指针。
3、关于defualt
关键字的思考
在C++11中,default是一个新的关键字,它的作用是告诉编译器使用默认的函数实现。在C++中,当我们定义一个类时,编译器会自动为我们生成一些默认的函数,例如默认构造函数、拷贝构造函数、析构函数等。但是,如果我们自己定义了某个函数,那么编译器就不会再为我们生成默认的函数了。在这种情况下,我们可以使用default关键字来告诉编译器使用默认的函数实现。例如:
class MyClass {
public:
MyClass() = default; // 使用默认的构造函数实现
MyClass(const MyClass& other) = default; // 使用默认的拷贝构造函数实现
~MyClass() = default; // 使用默认的析构函数实现
};
在C++20中,default关键字还可以用于声明特殊成员函数为“显式默认”的。这意味着编译器将不会自动为我们生成该函数的定义,而是要求我们显式地声明该函数为“default”。这样做的好处是可以更好地控制类的行为。例如:
class MyClass {
public:
MyClass() = default; // 显式声明默认构造函数
MyClass(const MyClass& other) = default; // 显式声明拷贝构造函数
~MyClass() = default; // 显式声明析构函数
MyClass(MyClass&& other) noexcept = default; // 显式声明移动构造函数为“显式默认”
MyClass& operator=(const MyClass& other) = default; // 显式声明拷贝赋值运算符为“显式默认”
MyClass& operator=(MyClass&& other) noexcept = default; // 显式声明移动赋值运算符为“显式默认”
};
4、关于在构造和析构函数中调用虚函数会产生的问题
在C++中,如果在构造函数或析构函数中调用虚函数,可能会导致一些未定义的行为和bug。 这是因为,在构造函数或析构函数中,对象的状态可能还没有完全初始化或已经被销毁,这可能会导致虚函数调用的行为不可预测。 例如,如果基类构造函数中的虚函数调用被解析成调用派生类的虚函数,而派生类的虚函数中又访问到未初始化的派生类数据,将导致程序出现一些未定义行为和bug。