C++单例模式与特殊类的设计

C++
1.3k words

设计模式

我们以前学的面向过程、面向对象,还有封装、继承、多态

这些就像是武功里面的内功,练到了会用了就是会,而不会想学也都是从0到1,一步步学习练习

而设计模式就像是武功里的外功,属于是学会了,但是要怎么样才能把这个功力的作用最大化,在哪些条件下用什么样的内功能破解,研究的是这个问题

例如,我想设计一个只能在栈或堆上实例化的类应该怎么做,我想设计一个不能被拷贝或继承的类应该怎么做,这些就是外功,是有固定章法的

但是在C++中,设计模式关注的内容没有那么多那么全,我们暂时只了解单例模式即可

特殊类的设计

不能被拷贝的类

在C++11中,引入了delete关键字,可以直接禁用拷贝构造和赋值重载

在C++98中,虽然没有这个关键字,但是我们可以直接把类的拷贝构造和赋值重载设置为私有即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// C++11
class A {
public:
A(int x = 1) :num(x) {};、
A(const A& a) = delete;
A& operator=(const A& a) = delete;
public:
int num;
};
// C++98
class A {
public:
A(int x = 1) :num(x) {}
public:
int num;
private:
A(const A& a) {}
A& operator=(const A& a) {}
};

需要注意的是拷贝构造也属于构造函数,当我们写了拷贝构造,就不会生成默认构造函数了,需要自己写

不能被继承的类

在C++11中,直接在类后面加final就能解决问题

在C++98中,将构造函数私有化也能达到目的,因为子类在构造的时候会调用基类的构造

但是这样会有一个问题,就是没办法直接进行实例化

我们可以借助静态成员函数来进行访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// C++11
class A final
{
public:
A() {};
}

// C++98
class A
{
public:
static A GetA() {
return A();
}
private:
A() {};
}

我们可以这样来创建A的实例

1
A a = A::GetA();

同时也实现了A不能被继承的特性

这是静态成员啊还能输的特性,但是静态成员啊还能输不能之际范围跟非静态成员或者非静态成员函数,并且他没有自己的this指针,本质上是静态区的内容

只在堆区创建的类

这种类的作用是防止对象被拷贝或赋值,只能通过指针传递

好处就是当类特别大时防止栈溢出,其次就是在一些设计模式中可能会用到

如果是采用指针进行管理,需要注意防止多重释放造成的内存泄漏问题

第一种思路就是,禁止直接使用构造函数,只能通过我给你的静态成员函数接口调用,而且必须设置为静态的,如果是非静态成员函数,就首先需要实例化一个对象才能调用,于是就循环调用了

1
2
3
4
5
6
7
8
9
10
11
12
class A {
public:
static A* GetA_Heap() {
return new A;
}
private:
A(int x = 1) :num(x) {};
A(const A& a) = delete;
A& operator=(const A& a) = delete;
public:
int num;
};

这里同样是需要将拷贝构造和赋值重载禁用的,为了避免意外的拷贝和赋值操作导致栈上也创建

同样的道理,私有化析构函数也能达到这个目的,也同样需要些一个静态成员函数提供接口

只在栈区创建的类

同样将构造函数私有化,只暴露接口给外面调用即可

需要注意的是,禁用new、new[]、delete、delete[]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A {
public:
static A GetA_Stack() {
return A();
}
private:
A(int x = 1) :num(x) {};
void* operator new(size_t size) = delete;
void* operator new[](size_t size) = delete;
void operator delete(void* ptr) = delete;
void operator delete[](void* ptr) = delete;
public:
int num;
};

单例模式

单例模式意思是只允许实例化一个对象的类

单例模式也有不同的实现思路,分别称之为饿汉模式和懒汉模式

饿汉模式指的是在类加载时就创建实例,因此线程安全,但也会导致一些不必要的资源消耗,读条时间会变久

懒汉模式指的是在首次使用时才创建实例,这样可以节省很多资源,但在多线程环境中需要注意线程安全的问题,需要引入互斥锁解决

饿汉模式

怎么样实现在程序启动时就自动创建实例呢,答案就是在成员变量中加入一个静态实例就可以

在类里面的静态实例主要起声明的作用,初始化静态实例需要在类外做

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Singleton {
public:
static Singleton& GetInstance() {
return instance;
}

void func() {
cout << "func" << endl;
}
private:
Singleton() {}
~Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;

static Singleton instance;
};

Singleton Singleton::instance;

懒汉模式

懒汉模式只需要在调用接口时判断一下是否初始化过即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Singleton {
public:
static Singleton* GetInstance() {
if (instance == nullptr)
instance = new Singleton();
return instance;
}

void func() {
cout << "func" << endl;
}
private:
Singleton() {}
~Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;

static Singleton* instance;
};

Singleton* Singleton::instance = nullptr;

需要注意的是,这里的线程是不安全的,需要设置互斥锁

Comments