开发者社区> 果冻虾仁> 正文

再议C++单例

简介: 再议C++单例
+关注继续查看

前情提要


在之前的文章C++的单例模式为什么不直接全部使用static,而是非要实例化一个对象?发布以后,陆陆续续收到很多网友留言提问,问题主要集中在文中的这一段:


class Singleton {
public:
    static void on() {Singleton::isOn = true;}
    static void off() {Singleton::isOn = false;}
    static bool state() {return Singleton::isOn;}
private:
    static bool isOn;
};
class Monitor: public Singleton {
public:
    static void addBrightness(int val) { brightness += val;}
    static void subBrightness(int val) { brightness -= val;}
    static int getBrightness() { return brightness;}
private:
    static int brightness;
};


如果有子类继承这一父类,来拓展成新的子类,比如Monitor显示器类有开关状态,同时扩展了一个亮度的成员。但是父子类的static成员变量是共享的,其isOn成员会有问题。


大家纷纷留言评论:“其isOn成员会有问题”,是指什么问题?会有什么问题?


甚至有很多网友是从其他公众号看到了我这篇文章,然后特地赶过来追问的。比如:


640.jpg


🧐 这位网友你是有多不想关注我……


进入正片


好了,我来解释一下。这上面的例子中isOn是父类Singleton中的静态成员,子类Monitor继承了父类后,isOn也还是同一个。如果这时候父类所代表的单例调用了on函数将isOn改为true。那么子类Monitor的isOn也会被修改,因为它们真的是同一个变量!不信你看:


单例继承写法1:


#include <iostream>
using namespace std;
class Singleton {
public:
    static void on() {Singleton::isOn = true;}
    static void off() {Singleton::isOn = false;}
    static bool state() {return Singleton::isOn;}
private:
    static bool isOn;
};
bool Singleton::isOn = true;
class Monitor: public Singleton {
public:
    static void addBrightness(int val) { brightness += val;}
    static void subBrightness(int val) { brightness -= val;}
    static int getBrightness() { return brightness;}
private:
    static int brightness;
};
int Monitor::brightness = 0;
int main() {
    Singleton::on();
    Monitor::on();
    cout<<"Singleton state:" << Singleton::state() 
        << " Monitor state:" << Monitor::state()<<endl;
    Singleton::off();
    cout<<"Singleton state:" << Singleton::state() 
        << " Monitor state:" << Monitor::state()<<endl;
}


输出:


Singleton state:1 Monitor state:1
Singleton state:0 Monitor state:0


但逻辑上两个类应该表示不同资源的“单例”,不应该互相影响才对。所以我说的有问题,不是说编译会有问题,而是说使用起来在逻辑上会有问题。有一种解法是你可以在子类里面再重新定义一个static的isOn变量及其相关的读写函数。这样父子类就解耦了。但是如果你要这样改的话,那么使用继承还有什么意义呢?完全没必要继承了啊。

好了,继续看。


单例继承写法2:


有的少年可能在此时会想起来,我上篇文章是在介绍Meyers' Singleton。于是想把isOn改成局部静态变量。那么这样是否就没问题呢?


#include <iostream>
using namespace std;
class Singleton {
public:
    static bool& getOn() {
        static bool isOn = true;
        return isOn;
    }
    static void on() {getOn() = true;}
    static void off() {getOn() = false;}
    static bool state() {return getOn();}
};
class Monitor: public Singleton {
public:
    static int& Brightness() {
        static int brightness = 0;
        return brightness;
    }
    static void addBrightness(int val) { Brightness() += val;}
    static void subBrightness(int val) { Brightness() -= val;}
    static int getBrightness() { return Brightness();}
};
int main() {
    Singleton::on();
    Monitor::on();
    cout<<"Singleton state:" << Singleton::state() 
        << " Monitor state:" << Monitor::state()<<endl;
    Singleton::off();
    cout<<"Singleton state:" << Singleton::state() 
        << " Monitor state:" << Monitor::state()<<endl;
}


同样有问题,虽然是局部静态变量了。但这种写法父子类使用的依旧是同一个isOn的静态变量!编译运行,输出还是:


Singleton state:1 Monitor state:1
Singleton state:0 Monitor state:0


单例继承写法3:


请注意并不是说使用了静态局部变量就是Meyers' Singleton。于是手快的少年,肯定立马写出了下一个版本:


#include <iostream>
using namespace std;
class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton inst;
        return inst;
    }
    static void on() {getInstance().isOn = true;}
    static void off() {getInstance().isOn = false;}
    static bool state() {return getInstance().isOn;}
protected:
    bool isOn = true;
};
class Monitor: public Singleton {
public:
    static Monitor& getInstance() {
        static Monitor inst;
        return inst;
    }
    static void addBrightness(int val) { getInstance().brightness += val;}
    static void subBrightness(int val) { getInstance().brightness -= val;}
    static int getBrightness() { return getInstance().brightness;}
private:
    int brightness = 0;
};
int main() {
    Singleton::on();
    Monitor::on();
    cout<<"Singleton state:" << Singleton::state() 
        << " Monitor state:" << Monitor::state()<<endl;
    Singleton::off();
    cout<<"Singleton state:" << Singleton::state() 
        << " Monitor state:" << Monitor::state()<<endl;
}


少年你接近了真相,但是还是不对。这也不是Meyers' Singleton。输出还是:


Singleton state:1 Monitor state:1
Singleton state:0 Monitor state:0


单例继承写法4:


少年:“我悟了”


#include <iostream>
using namespace std;
class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton inst;
        return inst;
    }
    void on() {isOn = true;}
    void off() {isOn = false;}
    bool state() {return isOn;}
protected:
    bool isOn = true;
};
class Monitor: public Singleton {
public:
    static Monitor& getInstance() {
        static Monitor inst;
        return inst;
    }
    void addBrightness(int val) { brightness += val;}
    void subBrightness(int val) { brightness -= val;}
    int getBrightness() { return brightness;}
private:
    int brightness = 0;
};
int main() {
    Singleton::getInstance().on();
    Monitor::getInstance().on();
    cout<<"Singleton state:" << Singleton::getInstance().state()
        << " Monitor state:" << Monitor::getInstance().state()<<endl;
    Singleton::getInstance().off();
    cout<<"Singleton state:" << Singleton::getInstance().state()
        << " Monitor state:" << Monitor::getInstance().state()<<endl;
}


再次编译运行之后,输出:


Singleton state:1 Monitor state:1
Singleton state:0 Monitor state:1


没错这才是正确写法,要注意Meyers' Singleton,除了局部静态变量、数据成员是非静态这些条件以外,如果你要读写其中的数据成员,一定要像普通类一样写普通成员函数,而非静态成员函数来操作。如果你不希望写这些成员函数,那你就直接把单例中的数据成员声明成public吧……

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
28851 0
阿里云服务器ECS登录用户名是什么?系统不同默认账号也不同
阿里云服务器Windows系统默认用户名administrator,Linux镜像服务器用户名root
16231 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
20568 0
腾讯云服务器 设置ngxin + fastdfs +tomcat 开机自启动
在tomcat中新建一个可以启动的 .sh 脚本文件 /usr/local/tomcat7/bin/ export JAVA_HOME=/usr/local/java/jdk7 export PATH=$JAVA_HOME/bin/:$PATH export CLASSPATH=.
14888 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,云吞铺子总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系统盘、创建快照、配置安全组等操作如何登录ECS云服务器控制台? 1、先登录到阿里云ECS服务器控制台 2、点击顶部的“控制台” 3、通过左侧栏,切换到“云服务器ECS”即可,如下图所示 通过ECS控制台的远程连接来登录到云服务器 阿里云ECS云服务器自带远程连接功能,使用该功能可以登录到云服务器,简单且方便,如下图:点击“远程连接”,第一次连接会自动生成6位数字密码,输入密码即可登录到云服务器上。
36395 0
阿里云服务器ECS远程登录用户名密码查询方法
阿里云服务器ECS远程连接登录输入用户名和密码,阿里云没有默认密码,如果购买时没设置需要先重置实例密码,Windows用户名是administrator,Linux账号是root,阿小云来详细说下阿里云服务器远程登录连接用户名和密码查询方法
22311 0
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
23565 0
+关注
61
文章
2
问答
文章排行榜
最热
最新
相关电子书
更多
JS零基础入门教程(上册)
立即下载
性能优化方法论
立即下载
手把手学习日志服务SLS,云启实验室实战指南
立即下载