工作和面试中的单例

单例是什么?单例是一种特殊的类,用于确保只有一个对象,同时提供一种全局访问这个对象的方法。最近在工作中体验了一把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》,高评分良心韩剧

解读:家人和朋友比自尊心更值得守护。

原文地址:https://www.cnblogs.com/zgwu/p/10428692.html

时间: 2024-10-14 15:10:06

工作和面试中的单例的相关文章

Swift中编写单例的正确方式

Swift中编写单例的正确方式 2015-12-07 10:23 编辑: yunpeng.hu 分类:Swift 来源:CocoaChina翻译活动 14 10647 Objective-CSwift单例 招聘信息: Cocos2d-x 工程师 cocos2dx手游客户端主程 wp开发 iOS开发工程师 iOS软件工程师 iOS研发工程师 iOS讲师 iOS开发工程师 iOS高级开发工程师 iOS 高级软件工程师 iOS高级开发工程师 本文由CocoaChina译者leon(社区ID)翻译自kr

笔记:Java中的单例设计模式

之前接触过单例模式,当初不明白这样的设计用意,今天特地研究了下java中的单例设计模式的用处及用法. 单例模式:单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例类的特殊类.一个类有且仅有一个实例,并且自行实例化向整个系统提供. 单例模式的用处:一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务:一个系统只能有一个窗口管理器或文件系统:一个系统只能有一个计时工具或ID(序号)生成器.如在Windows中就只能打开一个任务管理器.如果不使用机制对窗口对象进行唯一化,将

缓存中获取单例bean

前言 上一篇文章FactoryBean的使用实际上是为了Bean的加载的详细解析进行的介绍FactoryBean,从这篇文章开始,LZ会对Bean的加载过程进行详细的讲述,之前文章Bean的加载只是对Bean的加载过程进行了快速的大致上的过了一遍,详细的解析过程开始... 缓存中获取单例bean 前面的文章Bean的加载已经提到过,单例在Spring的同一个容器中只会被创建一次,后续再获取bean直接从单例缓存中获取,当然这里也只是尝试加载,首先尝试从缓存中加载,然后再尝试从singletonF

Java设计模式中的单例设计

/** * 单例设计模式 * 应用场合:只需要一个对象的 * 作用:保证整个应用程序中某个实例有且只有一个 * 类型有:饿汉模式.懒汉模式 * 下面的例子是一个饿汉模式的例子 */ class SingleDemo { // 1.将构造方法私有化,不允许外部直接创建使用 private SingleDemo() {} // 2.创建类的唯一实例,使用private static修饰 private static SingleDemo instance = new SingleDemo(); //

iOS开发——多线程OC篇&amp;多线程中的单例

多线程中的单例 1 #import "DemoObj.h" 2 3 @implementation DemoObj 4 5 static DemoObj *instance; 6 7 8 9 // 在iOS中,所有对象的内存空间的分配,最终都会调用allocWithZone方法 10 // 如果要做单例,需要重写此方法 11 // GCD提供了一个方法,专门用来创建单例的 12 + (id)allocWithZone:(struct _NSZone *)zone 13 { 14 sta

【Spring】8、Spring框架中的单例Beans是线程安全的么

看到这样一个问题:spring框架中的单例Beans是线程安全的么? Spring框架并没有对单例bean进行任何多线程的封装处理.关于单例bean的线程安全和并发问题需要开发者自行去搞定.但实际上,大部分的Spring bean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的.如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全. 最浅显的解决办法就是将多态bean的作用域由"singleton&

iOS中的单例你用对了?

单例模式怎么定义的,可能在不同的语言,不同的书中不完全一样,但是概况开来都应该是:一个类有且仅有一个实例,并且自行实例化向整个系统提供. 因此,首先你可能需要确定你是真的需要一个单例类,还是说仅仅是需要一个方便调用的实例化方法.如果你是真的需要一个单例类,那么你就应该确保这个单例类,有且仅有一个实例(不管怎么操作都只能获取到这个实例). 最近看到一些github上的单例使用,别人的用法,有一些思考,然后写demo测试了下,就这个简单的单例也有一些坑呢,希望能给他人一些提醒. Objective-

多个so中模板单例的多次实例化

在Android打包项目时,发现登录功能不能使用了,logcat中也没发现什么问题,最后一行一行log定位到了问题.原来是一个so文件中的构造函数被初始化二次!   这个单例是通过继承模板来实现的(暂时不考虑线程安全的问题) template<class T>class CSingleT{public: static T * Instance() { if (!ms_pObject) { ms_pObject = new T; } return ms_pObject; } static voi

馨予带你飞之unity中使用单例(二)

Unity中使用单例类 单例模式 Unity中单例类的创建 Unity中单例类的使用 单例模式 单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例类的特殊类.通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源.如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案. Unity中单例类的创建 using UnityEngine; using System.Collections; /// <summary>