使用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。

posted @ 2023-05-06 14:56  非法关键字  阅读(157)  评论(0编辑  收藏  举报