c++中static单例模式

c++中static单例模式

在《C++中的static》一文中,提到了局部的static声明变量,可以保证对象只被初始化一次,这种方式可以用来写单例模式。由于函数返回过程中会可能发生对象复制的情况,导致原本希望获得单例对象,最后却不如所愿,所以写单例模式的时候需要注意下,避免这种情况发生。

那么应该如何正确使用static写单例模式呢?

函数返回值与对象复制

C++函数返回值类型,接收变量的类型会影响函数返回过程中是否会发生对象的复制。

  1. 当函数返回值不是引用类型是,会发生复制。

    返回非引用类型
    1
    2
    3
    4
    5
    6
    7
    static Student GetInstance() {
    static Student stu;
    return stu;
    }

    Student s1 = Student::GetInstance(); // 会复制
    Student& s2 = Student::GetInstance(); // 异常,不能用引用类型接收返回值
  2. 当函数返回值是引用类型,接收函数返回值的变量不是引用时,仍然发生复制;函数返回值是引用类型,接收函数返回值的变量是引用类型,不会发生复制

    返回引用类型
    1
    2
    3
    4
    5
    6
    7
    static Student& GetInstance() {
    static Student stu;
    return stu;
    }

    Student s1 = Student::GetInstance(); // 仍然会复制
    Student& s1 = Student::GetInstance(); // 无复制

当你用上面第二种方式写单例,使用时候要特别注意,不要去用一个非引用的变量去接收返回值,否者往往出现不期望得到的结果。

单例错误的写法

错误写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>

class Student {
public:
static Student GetInstance() {
static Student stu;
printf("stu %p \n", &stu);
return stu;
}
int age;
std::string name;
};

int main() {
Student stu1 = Student::GetInstance();
Student stu2 = Student::GetInstance();
Student stu3 = Student::GetInstance();
printf("stu1 %p \n", &stu1);
printf("stu2 %p \n", &stu2);
printf("stu3 %p \n", &stu3);
}

输出结果

stu 0x55d80ed86040

stu 0x55d80ed86040

stu 0x55d80ed86040

stu1 0x7ffdd71828f0

stu2 0x7ffdd7182920

stu3 0x7ffdd7182950

可以看出使用GetInstance()获得的stu1,stu2,stu3并没有如愿以偿获得到单例的Student对象,上面代码中实际上每一次调用GetInstance()执行static Student stu;实际上只构建了一次对象,但是在对象返回时候由于发生了复制,最终接收到的都是重新构建的对象。

返回指针的方式

返回指针不推荐
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

class Student {
public:
static Student* GetInstance() {
static Student stu;
return &stu;
}
int age;
std::string name;
};

int main() {
Student stu1 = Student::GetInstance();
Student stu2 = Student::GetInstance();
Student stu3 = Student::GetInstance();
printf("stu1 %p \n", &stu1);
printf("stu2 %p \n", &stu2);
printf("stu3 %p \n", &stu3);
}

输出结果

stu1 0x556c9c248040

stu2 0x556c9c248040

stu3 0x556c9c248040

这样做有个缺陷,因为调用者如果使用delete instance会导致对象被提前销毁。

所以建议使用返回引用的方式。

返回引用的方式

推荐写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>

class Student {
public:
~Student(){}
Student(const Student&) = delete;
Student& operator=(const Student&) = delete;
static Student& GetInstance() {
static Student stu;
return stu;
}
int age;
std::string name;
private:
Student(){}
};

int main() {
Student& stu1 = Student::GetInstance();
Student& stu2 = Student::GetInstance();
Student& stu3 = Student::GetInstance();
printf("stu1 %p \n", &stu1);
printf("stu2 %p \n", &stu2);
printf("stu3 %p \n", &stu3);
}

注意,实例中为了避免调用者使用非引用的变量去接收返回值,将拷贝构造函数标记=delete, 禁止调用。

1
2
Student(const Student&) = delete;
Student& operator=(const Student&) = delete;

综上所述,建议使用最后提到的返回引用的方式来创建单例模式。



关注博客或微信搜索公众号多媒体与图形,获取更多内容,欢迎在公众号留言交流!
扫一扫关注公众号
作者

占航

发布于

2022-11-13

更新于

2023-10-04

许可协议

评论