概述
作用域可以分为 类作用域、类名的作用域 以及 对象的作用域 几部分内容。
在类中定义的成员变量和成员函数的作用域是整个类,这些名称只有在类中(包含类的定义部分和类外函数实现部分)是可见的,在类外是不可见的,因此,可以在不同类中使用相同的成员名。另外,类作用域意味着不能从外部直接访问类的任何成员,即使该成员的访问权限是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设计模式有如下优点:
- 提高编译速度;
- 实现信息隐藏;
- 减小编译依赖,可以用最小的代价平滑的升级库文件;
- 接口与实现进行解耦(降低耦合度);
- 移动语义友好。
代码实现:
/*******************************************
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();
}