单例是什么? 单例是一种特殊的类, 用于确保只有一个对象, 同时提供一种全局访问这个对象的方法. 最近在工作中体验了一把 5 分钟将一个类改造成单例, 感觉还是蛮不错的, 所以我决定写一篇文章, 跟大家交流技术和经验.
单例的原理是利用 C++ 中的静态成员变量和静态成员函数, 同时禁用构造函数的方法, 达到只有一个对象实例的目的.
具体来说, 设计一个单例的要点如下:
(1)类的静态成员变量是该类的指针.
(2)类的静态成员函数负责返回唯一的实例, 当 (1) 中的指针不为空时就直接返回, 否则为该指针 new 一个对象.
(3)类的默认构造函数访问权限设计为非 public 的(如 protected 或 private).
(4)禁用类的拷贝构造函数和赋值运算符函数(可以设置为 private 而不实现, 或者使用 C++11 的 = delete 语法).
可见,(3)和 (4) 是为了阻止通过其他方法创建对象, 因此只能通过 (2) 中提供的静态方法获得实例. 以下是一个单例的代码:
- #include <cstdio>
- #include <string>
- // 一个简单的单例
- class Singleton {
- public:
- static Singleton* Instance() {
- if (singleton_ != NULL) {
- return singleton_;
- }
- singleton_ = new Singleton();
- return singleton_;
- }
- void SetData(std::string data) {
- singleton_->data_ = data;
- }
- std::string GetData() {
- return singleton_->data_;
- }
- private:
- Singleton() {}
- Singleton(const Singleton& other);
- Singleton& operator=(const Singleton& other);
- private:
- static Singleton* singleton_;
- std::string data_;
- };
- // 初始化类的静态成员变量指针
- Singleton* Singleton::singleton_ = NULL;
- int main(int argc, char** argv) {
- // 创建对象
- Singleton* instance = Singleton::Instance();
- instance->SetData("Singleton test");
- Singleton* instance2 = Singleton::Instance();
- Singleton* instance3 = Singleton::Instance();
- printf("%s\n", instance->GetData().c_str());
- printf("%s\n", instance2->GetData().c_str());
- printf("%s\n", instance3->GetData().c_str());
- return 0;
- }
说起单例, 还要聊聊全局变量. 阅读代码时有一种体验叫 "全局变量厌烦症"(惭愧, 我自创的), 这种感觉就像在车站或商场大家在有序排队, 突然有特权分子要强行插队一样糟糕. 全局变量就像代码里的特权分子, 它在使用之前不需要构造, 而且会污染名字空间. 而单例则没有这些缺点, 单例本质上是一个特殊的类, 使用单例时仍需要先构造再使用.
如何将一个现有的类快速改造成单例呢? 答案是只需要给这个类增加一个静态成员指针(该类自身的指针), 同时提供一个静态的 Set 和 Get 方法, Set 方法用于将一个普通的对象指针传递给静态成员指针, Get 方法直接返回静态成员指针. 如下所示:
- #include <cstdio>
- #include <string>
- #include <vector>
- namespace tools {
- // 字符串转换工具类
- class Translate {
- public:
- void Init(bool tolower_flag) {
- tolower_flag_ = tolower_flag;
- }
- void Work(const std::string src, std::string* dst);
- static void SetTranslate(Translate* translate) {
- translate_ = translate;
- }
- static Translate* GetTranslate() {
- return translate_;
- }
- private:
- bool tolower_flag_;
- static Translate* translate_;
- };
- Translate* Translate::translate_ = NULL;
- void Translate::Work(const std::string src,
- std::string* dst) {
- if (tolower_flag_) {
- for (size_t i = 0; i <src.length(); ++i) {
- dst->push_back(tolower(src[i]));
- }
- dst->push_back('\0');
- } else {
- *dst = src;
- }
- }
- } // namespace tools
- namespace App {
- // 示例: 一个不能修改参数的函数
- void DoNotModifyMyParameters(const std::string data) {
- std::string mydata;
- // 获取工具类实例
- tools::Translate* translate = tools::Translate::GetTranslate();
- translate->Work(data, &mydata);
- printf("%s\n", mydata.c_str());
- }
- } // namespace App
- int main(int argc, char** argv) {
- tools::Translate translate;
- translate.Init(true);
- tools::Translate::SetTranslate(&translate);
- std::string data = "Test TEST";
- App::DoNotModifyMyParameters(data);
- return 0;
- }
细心的同学会发现, 以上做法并不能保证这个类只生成一个对象, 这也能叫单例吗? 是的, 这个类不是设计模式中的单例, 但是在工程场景中, 它就是单例. 实际工程中有些类的对象只会被创建一份, 但是并没有被设计成单例. 所以 "单例" 是由类以外的代码保证的.
我们这样做的目的是为了方便在其他函数中使用这个类, 而又不用为函数增加新的参数(真实情况是那个函数由于设计原因, 不支持传新的参数, 没听说过对吧? 但它是存在的, 在此先挖个坑, 限于篇幅就不扩展开了, 以后再填它).
简而言之, 工作中写单例, 追求简单粗暴有效. 虽然它不符合《设计模式》里规定的一些条件, 属于 "投机取巧", 不是一个完美的设计, 但是真的很好用啊.
最后聊聊面试中的单例, 常见问题有如何设计一个可以被继承的单例? 多线程中如何安全的初始化单例? 第一个问题比较简单, 只要注意将基类的构造函数设计为 protected, 同时析构函数设置为 virtual 就好了. 第二个问题注意不要使用 pthread_mutex_lock+double check 的做法, 由于编译器会指令重排, 因此上述做法是行不通的. 正确做法是使用 pthread_once 来保证初始化代码只被执行一次. 感兴趣的朋友可以搜索网上的相关博客文章, 阅读代码并亲自尝试. 面试主要靠积累, 其次看运气, 最终还是态度和细节决定成败.
金句分享
人真正变强大, 不是因为守护着自尊心, 而是抛开自尊心的时候.
-- 出自《请回答 1988》, 高评分良心韩剧
解读: 家人和朋友比自尊心更值得守护.
来源: http://www.bubuko.com/infodetail-2966990.html