单例模式的使用相当广泛,当一个类只允许被构造一个对象的时候尤为常见
禁止外面直接调用构造器
禁止被拷贝和移动
// 从设计上禁止行为
MyClass a;
MyClass *p = new MyClass;
MyClass b(a);
MyClass c(std::move(a));
c++中如果不想因为调用某个函数而产生某种效果,那么你就不应该声明这个函数,但是这个存在5个例外,五大函数如果你不定义它,编译器会默认生成空函数体的五大函数,已完成C++自己的内存管理机制。
MyClass() {}
MyClass(const MyClass &) = delete;
MyClass(MyClass &&) = delete;
void operator=(const MyClass &) = delete;
void operator=(MyClass &&) = delete;
单例的一般调用方式为:
#include "myclass.hpp"
int main() {
MyClass::getInstance().print();
return 0;
}
单例模式分为饿汉型和懒汉型
饿汉型:
在程序一起来的时候就加载到.data区,绝对线程安全
对象型:
// myclass.hpp
class MyClass {
public:
~MyClass() {}
static MyClass& getInstance() { return me_; }
void print() { ::printf("hello\n"); }
private:
MyClass() { ::printf("new\n"); }
MyClass(const MyClass&) = delete;
MyClass(MyClass&&) = delete;
void operator=(const MyClass&) = delete;
void operator=(MyClass&&) = delete;
private:
static MyClass me_;
};
// 假如在myclass.cpp文件中定义,定义有且仅有一次,不管你在哪,只要能最终被链接器成功link
#include "myclass.hpp"
MyClass MyClass::me_{};
指针型:
class MyClass {
public:
~MyClass() {}
static MyClass& getInstance() { return *me_; }
void print() { ::printf("hello\n"); }
private:
MyClass() { ::printf("new\n"); }
MyClass(const MyClass&) = delete;
MyClass(MyClass&&) = delete;
void operator=(const MyClass&) = delete;
void operator=(MyClass&&) = delete;
private:
static MyClass *me_;
};
// 假如在myclass.cpp文件中定义,定义有且仅有一次,不管你在哪,只要能最终被链接器成功link
#include "myclass.hpp"
MyClass *MyClass::me_{ new MyClass }; // 同种类型互为友元,可访问自己的私有属性和操作,构造指针对象时构造实际对象
懒汉型:
在函数getInstance()
被调用的时候就加载到.data区,线程安全需要讨论
对象型:
线程安全,static对象只能创建一次,底层包含call_once
机制
// myclass.hpp
class MyClass {
public:
~MyClass() {}
static MyClass& getInstance() {
static MyClass me_;
return me_;
}
void print() { ::printf("hello\n"); }
private:
MyClass() { ::printf("new\n"); }
MyClass(const MyClass&) = delete;
MyClass(MyClass&&) = delete;
void operator=(const MyClass&) = delete;
void operator=(MyClass&&) = delete;
};
指针型:
class MyClass {
public:
~MyClass() {}
static MyClass& getInstance() {
if (nullptr == me_) { // [1]
me_ = new MyClass; // [2]
}
return *me_; // [3]
}
void print() { ::printf("hello\n"); }
private:
MyClass() { ::printf("new\n"); }
MyClass(const MyClass&) = delete;
MyClass(MyClass&&) = delete;
void operator=(const MyClass&) = delete;
void operator=(MyClass&&) = delete;
private:
static MyClass* me_;
};
// 假如在myclass.cpp文件中定义,定义有且仅有一次,不管你在哪,只要能最终被链接器成功link
#include "myclass.hpp"
MyClass *MyClass::me_{ nullptr }; // 构造指针对象时,并未产生实际对象
但多线程调用时,线程1执行[1]时2发现me_是null,正打算执行[2]时,时间片流转到了线程2,线程2也发现me_是null,在他的时间里做了new的操作后,又轮到了线程1去执行[2],此时就创建了两个对象,造成内存泄露
修改方法:
class MyClass {
public:
~MyClass() {}
static MyClass& getInstance() {
std::call_once(once_, [&]{ me_ = new MyClass; });
return *me_;
}
void print() { ::printf("hello\n"); }
private:
MyClass() { ::printf("new\n"); }
MyClass(const MyClass&) = delete;
MyClass(MyClass&&) = delete;
void operator=(const MyClass&) = delete;
void operator=(MyClass&&) = delete;
private:
static std::once_flag once_;
static MyClass* me_;
};
tip: 普通的双重判断可能存在CPU执行的指令和我们现象顺序不同的情况,需要人为设置内存操作顺序,这里不讨论那种解法