C++ Primer 学习笔记_24_类与数据抽象(10)--static 与单例模式、auto_ptr与单例模式、const成员函数、const 对象、mutable修饰符

C++ Primer 学习笔记_24_类与数据抽象(10)--static 与单例模式、auto_ptr与单例模式、const成员函数、const 对象、mutable修饰符

前言

【例】写出面向对象的五个基本原则?

解答:单一职责原则,开放封闭原则,依赖倒置原则,接口隔离原则和里氏替换原则

里氏替换原则:子类型必须能够替换他们的基类型。

设计模式分为三种类型:创建型模式、结构型模式和行为型模式

一、static 与单例模式

1、单例模式

单例模式的意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

(1)第一种形式

#include <iostream>
#include <string>
using namespace std;

class Singleton
{
private:
    static Singleton s;
    int i;
    Singleton(int x): i(x) {cout << "Singleton" << endl;}
    Singleton & operator = (Singleton&); //不允许赋值
    Singleton(const Singleton&); //不允许拷贝
public:
    static Singleton& instance() {return s;}
    int getValue() {return i;}
    void setValue(int x) {i = x;}
};

Singleton Singleton::s(47); //定义静态成员s

int main()
{
    Singleton& s = Singleton::instance();
    cout << s.getValue() << endl;
    Singleton& s2 = Singleton::instance();
    s2.setValue(9);
    cout << s.getValue() << endl;
    return 0;
}

运行结果:

Singleton

47

9

上述实现通过返回一个引用实现单列模式。如果返回的是一个指针而不是引用,用户可能会不小心删除此指针,因此上述实现比返回指针更安全。

(2)第二种形式

#include <iostream>
#include <string>
using namespace std;

class Singleton
{
private:
    int i;
    Singleton(int x): i(x) {cout << "Singleton" << endl;}
    void operator=(Singleton&);
    Singleton(const Singleton&); //不允许拷贝
public:
    static Singleton& instance()
    {
        static Singleton s(47);
        return s;
    }
    int getValue() {return i;}
    void setValue(int x) {i = x;}
};

int main()
{
    Singleton& s = Singleton::instance();
    cout << s.getValue() << endl;
    Singleton& s2 = Singleton::instance();
    s2.setValue(9);
    cout << s.getValue() << endl;
    return 0;
}

运行结果:

Singleton

47

9

上述方法时通过成员函数内部的静态对象的创建实现单例模式。

注意上述两种方法并未考虑线程安全的问题,如需应用在多线程环境下,需要加锁。

【例子:禁止拷贝,赋值,默认构造函数创建对象操作】

#include <iostream>
using namespace std;
class Singleton
{
public:
    static Singleton *GetInstance()
    {
        if (instance_ == NULL)
        {
            instance_ = new Singleton;
        }
        return instance_;
    }
    ~Singleton()
    {
        cout << "~Singleton ..." << endl;
    }
private:
    Singleton(const Singleton &other);  //将拷贝函数放在private,禁止拷贝
    Singleton &operator=(const Singleton &other);  //禁止赋值
    Singleton()  //禁止创建对象
    {
        cout << "Singleton ..." << endl;
    }
    static Singleton *instance_;
};
Singleton *Singleton::instance_;
int main(void)
{
    //Singleton s1;  // Error,调用默认构造函数
    Singleton *s1 = Singleton::GetInstance();
    Singleton *s2 = Singleton::GetInstance();
    //Singleton s3(*s1);        // Error,调用拷贝构造函数
    return 0;
}

运行结果:

Singleton ...

上述程序虽然调用了两个GetInstance函数,但只调用一次构造函数,即创建一个对象。将赋值运算符和拷贝构造函数声明为私有,禁止拷贝。但程序存在一个问题就是对象生存期到时不会被析构。

2、为了解决对象不会被析构的问题,可以使用一个静态的嵌套类对象来解决:

#include <iostream>
using namespace std;
class Singleton
{
public:
    static Singleton *GetInstance()
    {
        if (instance_ == NULL)
        {
            instance_ = new Singleton;
        }
        return instance_;
    }
    ~Singleton()
    {
        cout << "~Singleton ..." << endl;
    }
    class Garbo
    {
    public:
        ~Garbo()
        {
            if (Singleton::instance_ != NULL)
            {
                delete instance_;
            }
        }
    };
private:
    Singleton(const Singleton &other);  //将拷贝函数放在private,禁止拷贝
    Singleton &operator=(const Singleton &other);  //禁止赋值
    Singleton()  //禁止创建对象
    {
        cout << "Singleton ..." << endl;
    }
    static Singleton* instance_;
    static Garbo garbo_;    // 利用对象的确定性析构
};
Singleton::Garbo Singleton::garbo_;
Singleton* Singleton::instance_;
int main(void)
{
    //Singleton s1;  // Error,调用默认构造函数
    Singleton *s1 = Singleton::GetInstance();
    Singleton *s2 = Singleton::GetInstance();
    //Singleton s3(*s1);        // Error,调用拷贝构造函数
    return 0;
}

运行结果:

Singleton ...

~Singleton ...

解释:利用静态嵌套对象的确定性析构会调用Garbo类的析构函数,在析构函数内delete 单例类的指针。

3、上面办法比较繁琐,也可以返回局部静态对象的引用来解决:

#include <iostream>
using namespace std;
class Singleton
{
public:
    static Singleton& GetInstance()
    {
        static Singleton instance;      // 局部静态对象
        return instance;
    }
    ~Singleton()
    {
        cout << "~Singleton ..." << endl;
    }
private:
    Singleton(const Singleton &other);  //将拷贝函数放在private,禁止拷贝
    Singleton &operator=(const Singleton &other);  //禁止赋值
    Singleton()  //禁止创建对象
    {
        cout << "Singleton ..." << endl;
    }
};
int main(void)
{
    Singleton& s1 = Singleton::GetInstance();
    Singleton& s2 = Singleton::GetInstance();
    return 0;
}

运行结果:

Singleton ...

~Singleton ...

解释:局部静态对象只会初始化一次,所以调用多次GetInstance函数得到的是同一个对象。由于函数内使用了静态对象,故不是线程安全的。

4、实际上也可以使用auto_ptr 智能指针来解决,程序如下,更详细的对auto_ptr将在后续讨论。

#include <iostream>
#include<memory>
using namespace std;

class Singleton
{
public:
    static Singleton *GetInstance()
    {
        if (instance_.get() == NULL)
        {
            instance_ = auto_ptr<Singleton>(new Singleton);
        }
        return instance_.get();
    }

    ~Singleton()
    {
        cout << "~Singleton ..." << endl;
    }
private:
    Singleton(const Singleton &other);
    Singleton &operator=(const Singleton &other);
    Singleton()
    {
        cout << "Singleton ..." << endl;
    }
    static auto_ptr<Singleton> instance_;
};

auto_ptr<Singleton> Singleton::instance_;

int main(void)
{
    Singleton *s1 = Singleton::GetInstance();
    Singleton *s2 = Singleton::GetInstance();
    return 0;
}

运行结果:

Singleton ...

~Singleton ...

5、饿汉式单例模式(在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快)

实际上,上述所有的单例模式例子都不是线程安全的,设想如果两个线程同时运行到语句if (instance
== null),而此时该实例的确没有创建,那么两个线程都会创建一个实例。如果不希望加锁实现线程安全,可以使用饿汉模式(即在main函数之前先生成一个实例):

#include <iostream>
using namespace std;
class Singleton
{
public:
    static const Singleton* GetInstance()
    {
        return instance_;
    }
    ~Singleton()
    {
        cout << "~Singleton ..." << endl;
    }
    class Garbo
    {
    public:
        ~Garbo()
        {
            if (Singleton::instance_ != NULL)
            {
                delete instance_;
            }
        }
    };
private:
    Singleton(const Singleton &other);  //将拷贝函数放在private,禁止拷贝
    Singleton &operator=(const Singleton &other);  //禁止赋值
    Singleton()  //禁止创建对象
    {
        cout << "Singleton ..." << endl;
    }
    static const Singleton* instance_;
    static Garbo garbo_;    // 利用对象的确定性析构
};

const Singleton* Singleton::instance_ = new Singleton();
Singleton::Garbo Singleton::garbo_;

int main(void)
{
    //Singleton s1;  // Error,调用默认构造函数
    const Singleton *s1 = Singleton::GetInstance();
    const Singleton *s2 = Singleton::GetInstance();
    //Singleton s3(*s1);        // Error,调用拷贝构造函数
    return 0;
}

运行结果:

Singleton ...

~Singleton ...

6、或者通过加锁方式实现,详细将在后续讨论。

二、const成员函数、const 对象、mutable修饰符

1、const 成员函数

const成员函数不会修改对象的状态

const成员函数只能访问数据成员的值,而不能修改它

2、const 对象

如果把一个对象指定为const,就是告诉编译器不要修改它

const对象的定义:

const 类名 对象名(参数表);

const对象不能调用非const成员函数

3、mutable修饰

用mutable修饰的数据成员即使在const对象或在const成员函数中都可以被修改。

#include <iostream>
using namespace std;

class Test
{
public:
    Test(int x) : x_(x), outputTimes_(0) { }

    int GetX() const
    {
        cout << "const GetX ..." << endl;
        //x_ = 100;  //Error,尝试修改数据成员
        return x_;
    }

    int GetX()
    {
        cout << "GetX ..." << endl;
        return x_;
    }

    void Output() const
    {
        cout << "x=" << x_ << endl;
        outputTimes_++;
    }

    int GetOutputTimes() const
    {
        return outputTimes_;
    }
private:
    int x_;
    mutable int outputTimes_;  //mutable修饰
};

int main(void)
{
    const Test t(10);
    t.GetX();
    Test t2(20);
    t2.GetX();
    t.Output();
    t.Output();
    cout << t.GetOutputTimes() << endl;
    return 0;
}

运行结果:

const GetX ...

GetX ...

x=10

x=10

2

参考:

C++ primer 第四版

Effective C++ 3rd

http://blog.csdn.net/jnu_simba/article/details/9282235

http://blog.csdn.net/zjf280441589/article/details/24704603

C++编程规范

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-03 14:00:46

C++ Primer 学习笔记_24_类与数据抽象(10)--static 与单例模式、auto_ptr与单例模式、const成员函数、const 对象、mutable修饰符的相关文章

C++ Primer 学习笔记_22_类与数据抽象(8)--static 成员变量、static 成员函数、类/对象的大小

一.static 每个static数据成员是与类关联的对象,并不与该类的对象相关联!非static数据成员存在于类类型的每个对象中,static数据成员独立该类的任意对象存在. static成员函数没有this形参,它可以直接访问所属类的static成员,但是不能直接使用static成员! 1.static 成员变量 对于特定类型的全体对象而言,有时候可能需要访问一个全局的变量.比如说统计某种类型对象已创建的数量. 如果我们用全局变量会破坏数据的封装,一般的用户代码都可以修改这个全局变量,这时可

C++ Primer 学习笔记_19_类与数据抽象(5)_初始化列表(const和引用成员)、拷贝构造函数

C++ Primer 学习笔记_19_类与数据抽象(5)_初始化列表(const和引用成员).拷贝构造函数  从概念上将,可以认为构造函数分为两个阶段执行: 1)初始化阶段: 2)普通的计算阶段.计算阶段由构造函数函数体中的所有语句组成. 一.构造函数初始化列表 推荐在构造函数初始化列表中进行初始化 1.对象成员及其初始化 <span style="font-size:14px;">#include <iostream> using namespace std;

C++ Primer 学习笔记_56_类与数据抽象 --消息处理示例

复制控制 --消息处理示例 说明: 有些类为了做一些工作需要对复制进行控制.为了给出这样的例子,我们将概略定义两个类,这两个类可用于邮件处理应用程序.Message类和 Folder类分别表示电子邮件(或其他)消息和消息所出现的目录,一个给定消息可以出现在多个目录中.Message上有 save和 remove操作,用于在指定Folder中保存或删除该消息. 数据结构: 对每个Message,我们并不是在每个Folder中都存放一个副本,而是使每个Message保存一个指针集(set),set中

C++ Primer 学习笔记_57_类与数据抽象 --管理指针成员

复制控制 --管理指针成员 引言: 包含指针的类需要特别注意复制控制,原因是复制指针时只是复制了指针中的地址,而不会复制指针指向的对象! 将一个指针复制到另一个指针时,两个指针指向同一对象.当两个指针指向同一对象时,可能使用任一指针改变基础对象.类似地,很可能一个指针删除了一对象时,另一指针的用户还认为基础对象仍然存在.指针成员默认具有与指针对象同样的行为. 大多数C++类采用以下三种方法之一管理指针成员: 1)指针成员采取常规指针型行为:这样的类具有指针的所有缺陷但无需特殊的复制控制! 2)类

C++ Primer 学习笔记_55_类与数据抽象 --析构函数

复制控制 --析构函数 引言: 在构造函数中分配了资源之后,需要一个对应操作自动回收或释放资源.析构函数就是这样的一个特殊函数,它可以完成所需的资源回收,作为类构造函数的补充. 1.何时调用析构函数 撤销类对象时会自动调用析构函数: Sales_item *p = new Sales_item; { Sales_item item(*p); //调用复制构造函数 delete p; //调用指针p的析构函数 } //调用对象item的析构函数 动态分配的对象只有在指向该对象的指针被删除时才撤销,

C++ Primer 学习笔记_53_类与数据抽象 --友元、static成员

类 --友元.static成员 一.友元 友元机制允许一个类将对其非公有成员的访问权授予指定的函数或类(对未被授权的函数或类,则阻止其访问):友元的声明以关键字friend开始,但是它只能出现在类定义的内部.友元声明可以出现在类中的任何地方:友元不是授予友元关系的那个类的成员,所以它们不受其声明出现部分的访问控制影响. [最佳实践] 通常,将友元声明成组的放在类定义的开始或结尾是个好主意! 1.友元关系:一个例子 假设一个窗口管理类Window_Mgr可能需要访问由其管理的Screen对象的内部

C++ Primer 学习笔记_54_类与数据抽象 --复制构造函数、赋值操作符

复制控制 --复制构造函数.赋值操作符 引言: 当定义一个新类型时,需要显式或隐式地指定复制.赋值和撤销该类型的对象时会发生什么– 复制构造函数.赋值操作符和析构函数的作用!      复制构造函数:具有单个形参,该形参(常用const修饰)是对该类类型的引用.当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式的使用复制构造函数:当将该类型的对象传递给函数或者从函数返回该类型的对象时,将隐式使用复制构造函数.     析构函数:作为构造函数的互补,当对象超出作用域或动态分配的对象被删除

C++ Primer 学习笔记_16_类与数据抽象(2)_隐含的this指针

C++ Primer 学习笔记_16_类与数据抽象(2)_隐含的this指针 1.引言 在前面提到过,成员函数具有一个附加的隐含形参,即指向该类对象的一个指针.这个隐含形参命名为this. 2.返回*this 成员函数有一个隐含的附加形参,即指向该对象的指针,这个隐含的形参叫做this指针(编译器自动传递)使用this指针保证了每个对象可以拥有不同数值的数据成员,但处理这些成员的代码可以被所有对象共享.成员函数是只读的代码,由所有对象共享,并不占对象的存储空间,因为this指针指向当前对象,所以

C++ Primer 学习笔记_17_类与数据抽象(3)_类作用域

C++ Primer 学习笔记_17_类与数据抽象(3)_类作用域 引言: 每个类都定义了自己的新作用域与唯一的类型.即使两个类具有完全相同的成员列表,它们也是不同的类型.每个类的成员不同与任何其他类(或任何其他作用域)的成员. 一.类作用域中的名字查找 1)首先,在使用该名字的块中查找名字的声明.只考虑在该项使用之前声明的名字. 2)如果在1)中找不到,则在包围的作用域中查找. 如果找不到任何声明,则出错. 类的定义实际上是在两个阶段中处理: 1)首先,编译器声明: 2)只有在所有成员出现之后