C++类作用域(Pimpl模式,单例模式的自动释放)



概述

作用域可以分为 类作用域类名的作用域 以及 对象的作用域 几部分内容。

在类中定义的成员变量和成员函数的作用域是整个类,这些名称只有在类中(包含类的定义部分和类外函数实现部分)是可见的,在类外是不可见的,因此,可以在不同类中使用相同的成员名。另外,类作用域意味着不能从外部直接访问类的任何成员,即使该成员的访问权限是public,也要通过对象名来调用,对于static成员函数, 要指定类名来调用

如果发生“屏蔽”现象,类成员的可见域将小于作用域,但此时可借助 this指针“类名::” 形式指明所访问的是类成员,这有些类似于使用 “::”访问全局变量。例如:

#include <iostream>

using std::endl;
using std::cout;

int num = 1;

namespace nsp
{

int num = 2;

class Val
{
public:
    Val(int value)
    : num(value)
    {}

    void print(int num)
    {
        cout << "形参 num = " << num << endl;
        cout << "数据成员 num = " << this->num << endl; 
        cout << "数据成员 num = " << Val::num << endl; 
        cout << "命名空间nap里的 num = " << nsp::num << endl;
        cout << "全局变量 num = " << ::num << endl;
    }

private:
    int num;
};

};//end of namespace nsp

int main()
{
    nsp::Val v(10);
    v.print(20);
    return 0;
}

和函数一样,类的定义没有生存期的概念,但类定义有 作用域可见域。使用类名创建对象时,首要的前提是类名可见,类名是否可见取决于 类定义的可见域,该可见域同样包含在其作用域中,类本身可被定义在3种作用域内,这也是 类定义的作用域



全局作用域

在函数和其他类定义的外部定义的类称为全局类,绝大多数的C++类是定义在该作用域中,我们在前面定义的所有类都是在全局作用域中,全局类具有 全局作用域



类作用域

一个类可以定义在另一类的定义中,这是所谓 嵌套类 或者 内部类,举例来说,如果类A定义在类B中,如果A的访问权限是public,则A的作用域可认为和B的作用域相同,不同之处在于必须使用B::A的形式访问A的类名。当然,如果A的访问权限是private,则只能在类内使用类名创建该类的对象,无法在外部创建A类的对象

#include <iostream>

using std::endl;
using std::cout;

class Line
{
public:
    Line(int x1, int y1, int x2, int y2)
    : _pt1(x1, y1)
    , _pt2(x2, y2)
    {}

    void linePrint() const
    {
        _pt1.pointPrint();
        cout << "  ↓" << endl;
        _pt2.pointPrint();
    }   

private:
    
    //嵌套类 Point
    class Point
    {
    public:
        Point(int x, int y)
        : _ix(x)
        , _iy(y)
        {}

        void pointPrint() const
        {
            cout << "(" << _ix << ", " << _iy << ")" << endl;
        }
        
    private:
        //Point数据成员
        int _ix;
        int _iy;
    };
    
    //Line数据成员
    Point _pt1;
    Point _pt2;
    
};

int main()
{
    Line l(1, 2 ,3, 4);
    l.linePrint();
    
    //只能在Line类的内部使用内部类Point创建对象,Line外部无法创建内部类对象
    //Line::Point pt(1, 2);
    return 0;
}



设计模式之Pimpl

PIMPL(Private Implementation 或 Pointer to Implementation)是 通过一个私有的成员指针,将指针所指向的类的内部实现数据进行隐藏。PIMPL又称作 “编译防火墙”,它的实现中就用到了嵌套类。

PIMPL设计模式有如下优点:

  1. 提高编译速度;
  2. 实现信息隐藏;
  3. 减小编译依赖,可以用最小的代价平滑的升级库文件;
  4. 接口与实现进行解耦(降低耦合度);
  5. 移动语义友好。

代码实现:

/*******************************************
Line.h 文件
*******************************************/
#ifndef __LINE_H__
#define __LINE_H__

//外部类(使用者)
class Line
{
public:
    Line(int, int, int, int);
    void linePrint() const;
    ~Line();
    //内部类
    class LineImpl;

private:
	//指向内部类对象
    LineImpl * _pimpl;
};

#endif

/*******************************************
Line.cc 文件
*******************************************/
#include "Line.h"
#include <iostream>

using std::endl;
using std::cout;

//内部类实现
class Line::LineImpl
{
public:
    LineImpl(int x1, int y1, int x2, int y2)
    : _pt1(x1, y1)
    , _pt2(x2, y2)
    {
        cout << "LineImpl" << endl;
    }

    void printLineImpl() const
    {
        _pt1.pointPrint();
        cout << "  ↓" << endl;
        _pt2.pointPrint();
    }
    
    ~LineImpl()
    {
        cout << "~LineImpl" << endl;                       
    }
private:
    
    //Line内部类LineImpl的内部类(操作数据)
    class Point
    {
    public:
        Point(int x, int y)
        : _ix(x)
        , _iy(y)
        {
            cout << "Point()" << endl;                    
        }

        void pointPrint() const
        {
            cout << "(" << _ix << ", " << _iy << ")" << endl;                           
        }

        ~Point()
        {
            cout << "~Point()" << endl;                                        
        }

    private:
        int _ix;
        int _iy;
    };

    Point _pt1;
    Point _pt2;
};

Line::Line(int x1, int y1, int x2, int y2)
: _pimpl(new LineImpl(x1, y1, x2, y2))
{
    cout << "Line()" << endl;
}

void Line::linePrint() const
{
    _pimpl->printLineImpl();
}

Line::~Line()
{
    cout << "~Line()" << endl;
    if(_pimpl)
    {
        delete _pimpl;
        _pimpl = nullptr;
    }
}


/*******************************************
测试文件
*******************************************/
#include "Line.h"
#include <iostream>

using std::endl;
using std::cout;

int main()
{
    Line line(1, 2, 3, 4);
    line.linePrint();
    return 0;
}



单例模式的自动释放

之前的单例模式中,其中对象是由 _pInstance 指针来保存的, 而在使用单例设计模式的过程中,也难免会遇到内存泄漏的问题。那么是否有一个方法,可以 让对象自动释放,而不需要程序员自己手动去释放呢?嵌套类就可以完美的解决这一问题。

在涉及到自动的问题时,很自然的可以想到:当对象被销毁时,会自动调用其析构函数。利用这一特性,就可以解决。

以下是分别是四种方法:

1. 新建友元类的析构

#include <iostream>

using std::cout;
using std::endl;

class AutoRelease;

class Singleton
{
    friend AutoRelease;
public:
    static Singleton *getInstance() 
    {
        if(nullptr == _pInstance)
        {
            _pInstance = new Singleton();
        }

        return _pInstance;
    }

	/**由 AutoRelease类的显式析构函数来清理 Singleton类申请的堆内存
	//单例模式需要调用destroy才可以释放堆内存
    static void destroy()
    {
        if(_pInstance)
        {
            delete _pInstance;
            _pInstance = nullptr;
        }
    }
    */

private:
    Singleton()
    {
    	cout << "Singleton()" << endl;
    }
    ~Singleton() 
    {
    	cout << "~Singleton()" << endl;
    }
private:
    static Singleton *_pInstance;
};

Singleton *Singleton::_pInstance = nullptr;

//Singleton类的友元类,将 destroy函数放到其类的析构函数
//其创建的对象最后调用析构函数时,同时销毁 Singleton类的堆空间(并调用 Singleton类的析构函数)
class AutoRelease
{
public:
    AutoRelease()
    {
        cout << "AutoRelease()" << endl;
    }

    ~AutoRelease()
    {
        cout << "~AutoRelease()" << endl;
        if(Singleton::_pInstance)
        {
            delete Singleton::_pInstance;
            Singleton::_pInstance = nullptr;
        }
    }
};
int main(void)
{
    Singleton::getInstance();
    AutoRelease ar;
    return 0;
}

2. 内部类(静态成员)的析构

#include <iostream>

using std::cout;
using std::endl;


class Singleton
{
public:
    static Singleton *getInstance()
    {
        if(nullptr == _pInstance)
        {
            _pInstance = new Singleton();

        }
        return _pInstance;
    }

private:
    class AutoRelease
    {
    public: 
        AutoRelease()
        {
            cout << "AutoRelease()" << endl;
        }

        ~AutoRelease()
        {
            cout << "~AutoRelease()" << endl;
            if(Singleton::_pInstance)
            {
                //执行析构函数,调用operator delete
                delete Singleton::_pInstance;
                Singleton::_pInstance = nullptr;
            }
        }
    };

private:
    Singleton() 
    {
        cout << "Singleton()" << endl;
    }

    ~Singleton()
    {
        cout << "~Singleton()" << endl;
    }

private:
    static Singleton *_pInstance;
    static AutoRelease _auto;
};

Singleton *Singleton::_pInstance = nullptr;
Singleton::AutoRelease Singleton::_auto;

int main(void)
{
    Singleton::getInstance();
    //AutoRelease ar;
    return 0;
}

3. 使用atexit函数(饿人模式)

atexit() 函数

#include<stdlib.h> 
int atexit(void(*function)(void));

作用:用来注册执行 exit() 函数前执行的终止处理程序

atexit()用来注册终止处理程序,当程序通过调用 exit() 或从 main 中返回时被调用,终止处理程序执行的顺序和注册时的顺序是相反的,终止处理程序没有参数传递。同一个函数若注册多次,那它也会被调用多次。 按 POSIX.1-2001 规定,至少可以注册 32 个终止处理程序,若想查看实际可以注册多少个终止处理程序,可以通过调用 sysconf()函数获得。当一个子进程是通过调用 fork() 函数产生时,它将继承父进程的所有终止处理程序。在成功调用 exec 系列函数后,所有的终止处理程序都会被删除.

返回值: 成功返回 0,失败返回非 0 值。

注意点:

  • 如果一个进程被信号所中断,那由 atexit()函数注册的终止处理程序不会被调用
  • 如果在其中一个终止处理程序中调用了_exit()函数;那剩余的终止处理程序将不会得到调用,同时由 exit()函数调用的其他终止进程步骤也将不会执行
  • 在 POSIX.1-2001 标准中,在终止进程过程中,在终止处理程序中,不只一次的调用 exit()函数,这样导致的结果是未定义的.在某些系统中,这样的调用将会导致递归死循环.可移植的程序不应该在终止处理程序中调用 exit()函数
  • 函数 atexit()和 on_exit()在注册终止程序时,有一样的列表.在进程正常退出时执行终止处理程序,调用的顺序刚好与注册时相反
  • 按 POSIX.1-2001的规定,如果在终止处理程序中调用 longjmp()函数,这样导致的结果是未定义的
  • Linux 自 glibc2.2.3 版本后,atexit() 和 on_exit() 函数能够使用在共享库建立的函数,当共享库卸载时被调用

代码:

#include <stdlib.h> 
#include <iostream>

using std::cout;
using std::endl;


class Singleton
{
public:
    static Singleton *getInstance()
    {
    	//多线程初始化 _pInstance都为 nullptr,就会申请多个堆空间,但是都返回一个_pInstance,堆只释放一次
    	//多线程情况下,线程不安全,修改_pInstance的初始化
        if(nullptr == _pInstance)
        {
            _pInstance = new Singleton();
            atexit(destroy);
        }
        return _pInstance;
    }

    static void destroy()
    {
        if(_pInstance)
        {
            delete _pInstance;
            _pInstance = nullptr;
        }
    }

private:
    Singleton() 
    {
        cout << "Singleton()" << endl;
    }

    ~Singleton()
    {
        cout << "~Singleton()" << endl;
    }

private:
    static Singleton *_pInstance;
};

//饱汉模式:不到使用的时候就不创建;如果非要使用饱汉模式,就只能加锁操作
//Singleton *Singleton::_pInstance = nullptr; //饱人模式

//饿人模式,早于main函数,先申请一个堆空间,避免申请多个堆空间
Singleton *Singleton::_pInstance = getInstance(); 

int main(void)
{
    Singleton::getInstance();
    return 0;
}

4. 使用pthread_once()函数

在多线程环境中,有些事仅需要执行一次。通常当初始化应用程序时,可以比较容易地将其放在main 函数中。但当你写一个库时,就不能在 main 里面初始化了,你可以用 静态初始化,但使用 一次初始化(pthread_once) 会比较容易些。

int pthread_once(pthread_once_t *once_control, void(*init_routine)(void))

功能:本函数使用初值为PTHREAD_ONCE_INIT 的 once_control 变量 保证 init_routine()函数在本进程执行序列中仅执行一次

  • 类型为 pthread_once_t 的变量是一个控制变量。控制变量必须使用 PTHREAD_ONCE_INIT 宏静态地初始化。
  • pthread_once 函数首先检查控制变量,判断是否已经完成初始化,如果完成就简单地返回;否则,pthread_once 调用初始化函数,并且记录下初始化被完成。
    (如果在一个线程初始时,另外的线程调用 pthread_once,则调用线程等待,直到那个现成完成初始话返回。

参数

  • once_control 控制变量
  • init_routine 初始化函数

返回值: 若成功返回 0,若失败返回错误编号。


注释:

  • 在多线程编程环境下,尽管 pthread_once() 调用会出现在多个线程中,init_routine()函数仅执行一次,究竟在哪个线程中执行是不定的,是由内核调度来决定。
  • LinuxThreads 使用互斥锁和条件变量保证由 pthread_once()指定的函数执行且仅执行一次,而 once_control 表示是否执行过。
  • 如果 once_control的初值不是 PTHREAD_ONCE_INIT(LinuxThreads 定义为 0),pthread_once() 的行为就会不正常。
  • 在 LinuxThreads中,实际 “一次性函数” 的执行状态有三种:NEVER(0)、IN_PROGRESS(1)、DONE (2)
    • 如果once 初值设为1,则由于所有 pthread_once()都必须等待其中一个激发"已执行一次"信号,因此所有 pthread_once()都会陷入永久的等待中;
    • 如果设为 2,则表示该函数已执行过一次,从而所有 pthread_once() 都会立即返回 0。

适用范围:

这一般用于某个多线程调用的模块使用前的初始化,但是无法判定哪个线程先运行,从而不知道把初始化代码放在哪个线程合适的问题。一般的做法是把初始化函数放在main里,创建线程之前来完成,但是如果我们的程序最终不是做成可执行程序,而是编译成库的形式,那么 main 函数这种方式就没法做到了。

代码:

#include <stdlib.h>
#include <pthread.h>
#include <iostream>

using std::cout;
using std::endl;


class Singleton
{
public:
    static Singleton *getInstance()
    {
        pthread_once(&once, init);
        return _pInstance;
    }

    static void init()
    {
        _pInstance = new Singleton();
        atexit(destroy);
    }

    static void destroy()
    {
        if(_pInstance)
        {
            delete _pInstance;
            _pInstance = nullptr;
        }
    }

private:
    Singleton() 
    {
        cout << "Singleton()" << endl;
    }

    ~Singleton()
    {
        cout << "~Singleton()" << endl;
    }

private:
    static Singleton *_pInstance;
    static pthread_once_t once;
};

Singleton *Singleton::_pInstance = nullptr;
pthread_once_t Singleton::once = PTHREAD_ONCE_INIT;

int main(void)
{
    Singleton::getInstance();
    return 0;
}



块作用域

类的定义在代码块中,这是所谓 局部类,该类完全被块包含,其作用域仅仅限于定义所在块,不能在块外使用类名声明该类的对象。

void test() 
{
	class Point
	{
	public:
		Point(int x, int y)
		: _x(x), _y(y) 
		{} 
		
		void print() const
		{
			cout << "(" << _x
				 << "," << _y
				 << ")" << endl;
		}
		
	private:
		int _x;
		int _y;
	};
		
	Point pt(1, 2);
	pt.print();

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值