C++中的一些关键字

1、mutable关键字

只能用于类的非静态和非常量数据成员
 


类的const的成员函数,表示其不会改变对象(其实是指针this)的状态.

但是有些时候需要在该类函数中对数据成员进行赋值.这个时候就需要用到mutable关键字了

例如:
代码如下:
class Demo
{
public:
    Demo(){}
    ~Demo(){}
public:
    bool getFlag() const
    {
        m_nAccess++;
        return m_bFlag;
    }
private:
    int  m_nAccess;
    bool m_bFlag;
};
int main()
{
    return 0;
}



编译上面的代码会出现 error C2166: l-value specifies const object的错误
说明在const类型的函数中改变了类的非静态数据成员.
这个时候需要使用mutable来修饰一下要在const成员函数中改变的非静态数据成员
m_nAccess,代码如下:
代码如下:
class Demo
{
public:
    Demo(){}
    ~Demo(){}
public:
    bool getFlag() const
    {
        m_nAccess++;
        return m_bFlag;
    }
private:
    mutable int  m_nAccess;
    bool m_bFlag;
};
int main()
{
    return 0;
}


这样再重新编译的时候就不会出现错误了!


2、volatile

volatile是c/c++中一个鲜为人知的关键字,该关键字告诉编译器不要持有变量的临时拷贝,它可以适用于基础类型
如:int,char,long......也适用于C的结构和C++的类。当对结构或者类对象使用volatile修饰的时候,结构或者类的所有成员都会被视为volatile.
使用volatile并不会否定对CRITICAL_SECTION,Mutex,Event等同步对象的需要
例如:
int i;
i = i + 3;

无论如何,总是会有一小段时间,i会被放在一个寄存器中,因为算术运算只能在寄存器中进行。

一般来说,volatitle关键字适用于行与行之间,而不是放在行内。

我们先来实现一个简单的函数,来观察一下由编译器产生出来的汇编代码中的不足之处,并观察volatile关键字如何修正这个不足之处。

在这个函数体内存在一个busy loop(所谓busy loop也叫做busy waits,是一种高度浪费CPU时间的循环方法)

 代码如下:
void getKey(char* pch)
{
 while (*pch == 0)

}


当你在VC开发环境中将最优化选项都关闭之后,编译这个程序,将获得以下结果(汇编代码)
 代码如下:
;       while (*pch == 0)
$L27
 ; Load the address stored in pch
 mov eax, DWORD PTR _pch$[ebp]
 ; Load the character into the EAX register
 movsx eax, BYTE PTR [eax]
 ; Compare the value to zero
 test eax, eax
 ; If not zero, exit loop
 jne $L28


 jmp $L27
$L28
;}


这段没有优化的代码不断的载入适当的地址,载入地址中的内容,测试结果。效率相当的低,但是结果非常准确
现在我们再来看看将编译器的所有最优化选项开关都打开以后,重新编译程序,生成的汇编代码,和上面的代码
比较一下有什么不同
 代码如下:
;{ 
 ; Load the address stored in pch
 mov eax, DWORD PTR _pch$[esp-4]
 ; Load the character into the AL register
 movsx al, BYTE PTR [eax]
; while (*pch == 0)
 ; Compare the value in the AL register to zero
 test al, al
 ; If still zero, try again
 je SHORT $L84


;}


从代码的长度就可以看出来,比没有优化的情况要短的多。需要注意的是编译器把MOV指令放到了循环之外。这在单线程中是一个非常好的优化,但是,在多线程应用程序中,如果另一个线程改变了变量的值,则循环永远不会结束。被测试的值永远被放在寄存器中,所以该段代码在多线程的情况下,存在一个巨大的BUG。解决方法是重新写一次getKey函数,并把参数pch声明为volatile,代码如下:
 代码如下:
void getKey(volatile char* pch)
{
 while (*pch == 0)


}


这次的修改对于非最优化的版本没有任何影响,下面请看最优化后的结果:
 代码如下:
;{
 ; Load the address stored in pch
 mov eax, DWORD PTR _pch$[esp-4]
;       while (*pch == 0)
$L84:
 ; Directly compare the value to zero
 cmp BYTE PTR [eax], 0
 ; If still zero, try again
 je SHORT $L84
;}


这次的修改结果比较完美,地址不会改变,所以地址声明被移动到循环之外。地址内容是volatile,所以每循环之中它不断的被重新检查。
把一个const volatile变量作为参数传递给函数是合法的。如此的声明意味着函数不能改变变量的值,但是变量的值却可以被另一个线程在任何时间改变掉。


3、explicit

作用是"禁止隐形构造函数"被用于自动型别转换。

其中比较典型的例子就是容器类型,在这种类型的构造函数中你可以将初始长度作为参数传递给构造函数.

例如:
你可以声明这样一个构造函数
 代码如下:
class Array
{
public:
 explicit Array(int size);
 ......
};


在这里explicit关键字起着至关重要的作用,如果没有这个关键字的话,这个构造函数有能力将int转换成Array.一旦这种
情况发生,你可以给Array支派一个整数值而不会引起任何的问题,比如:
Array arr;
...
arr = 40;
此时,C++的自动型别转换会把40转换成拥有40个元素的Array,并且指派给arr变量,这个结果根本就不是我们想要的结果.如果
我们将构造函数声明为explicit,上面的赋值操作就会导致编译器报错,使我们可以及时发现错误.
需要注意的是:explicit同样也能阻止"以赋值语法进行带有转型操作的初始化";
例如:
 代码如下:
Array arr(40);//正确
Array arr = 40;//错误
看一下以下两种操作:
X x;
Y y(x);//显式类型转换
另一种
X x;
Y y = x;//隐式类型转换


这两种操作存在一个小小的差别,第一种方式式通过显式类型转换,根据型别x产生了型别Y的新对象;第二种方式通过隐式转换产生了一个型别Y的新对象.
explicit关键字的应用主要就是上面所说的构造函数定义种,参考该关键字的应用可以看看STL源代码,其中大量使用了该关键字


4、__based

__based 关键字使您能够基于指针(作为现有指针的偏移量的指针)声明指针。是基于指针地址的指针。

允许指针被定义为从某一点开始算的32位偏移值(对于64位的环境,是64 位偏移量),而不是内存的绝对位置


 例子代码如下:
typedef struct tagDEMOSTRUCT {
 int a;
 char sz[10];
} DEMOSTRUCT, * PDEMOSTRUCT;
HANDLE hFileMapping = CreateFileMapping(...);
LPVOID lpHead = (LPDWORD)MapViewOfFile(...);
DEMOSTRUCT __based(lpHead)* lpDemo;


DEMOSTRUCT只是随便定义的一个结构,用来代表任意的结构.

上面的例子声明了一个指针lpDemo,内部储存的是从lpHead开始的偏移值,也就是lpDemo是以lpHead为基准的偏移值.

虽然__based指针使用起来非常容易,但是,你必须在效率上付出一定的代价.每当你用__based指针处理数据,CPU都必须为它加上基地址,才能指向真正的位置.



5、__thread

__thread是GCC内置的线程局部存储设施,存取效率可以和全局变量相比。

__thread变量每一个线程有一份独立实体,各个线程的值互不干扰。

可以用来修饰那些带有全局性且值可能变,但是又不值得用全局变量保护的变量。


__thread使用规则:

不能修饰class类型,因为无法自动调用构造函数和析构函数,不能修饰函数的局部变量或者class的普通成员变量。

只能修饰POD类型(类似整型指针的标量,不带自定义的构造、拷贝、赋值、析构的类型,二进制内容可以任意复制memset,memcpy,且内容可以复原)。

可以用于修饰全局变量,函数内的静态变量,__thread变量值只能初始化为编译器常量(值在编译器就可以确定const int i=5,运行期常量是运行初始化后不再改变const int i=rand()).

 

#include<iostream>  
#include<pthread.h>  
#include<unistd.h>  
using namespace std;  
const int i=5;  
__thread int var=i;//两种方式效果一样  
//__thread int var=5;  
void* worker1(void* arg);  
void* worker2(void* arg);  
int main(){  
    pthread_t pid1,pid2;  
    //__thread int temp=5;  //不能修饰函数局部变量
    static __thread  int temp=10;//修饰函数内的static变量  
    pthread_create(&pid1,NULL,worker1,NULL);  
    pthread_create(&pid2,NULL,worker2,NULL);  
    pthread_join(pid1,NULL);  
    pthread_join(pid2,NULL);  
    cout<<temp<<endl;//输出10  
    return 0;  
}  
void* worker1(void* arg){  
    cout<<++var<<endl;//输出 6  
}  
void* worker2(void* arg){  
    sleep(1);//等待线程1改变var值,验证是否影响线程2  
    cout<<++var<<endl;//输出6  
} 

程序输出:

6

6         //可见__thread值线程间互不干扰

10


6、typename

(1)用在模板定义

标明其后的模板参数是类型参数

例如
 
template<typename  T, typename Y>
T foo(const T& t, const Y& y){//....};

templace<typename T>
class CTest
{
private:
 T t;
public:
 //...
}


其实,这里最常用的是使用关键字class,而且二者功能完全相同,这里的class和定义类时的class完全是两回事,C++当时就是为了减少关键字,才使用了class。但最终却不得不引入了typename,究竟是

什么原因呢?请看第二条,也就是typename的第二个用法。

(2) 模板中标识“内嵌类型”
这里有三个词,内嵌、依赖、类型名。那么什么是“内嵌依赖类型名(nested dependent type name)”?

请看SGI STL里的一个例子, 只是STL中count范型算法的实现:

template <class _InputIter, class _Tp>
typename iterator_traits<_InputIter>::difference_type
count(_InputIter __first, _InputIter __last, const _Tp& __value) {
  __STL_REQUIRES(_InputIter, _InputIterator);
  __STL_REQUIRES(typename iterator_traits<_InputIter>::value_type,
                 _EqualityComparable);
  __STL_REQUIRES(_Tp, _EqualityComparable);
  typename iterator_traits<_InputIter>::difference_type __n = 0;
  for ( ; __first != __last; ++__first)
    if (*__first == __value)
      ++__n;
  return __n;
}

这里有三个地方用到了typename:返回值、参数、变量定义。分别是:

typename iterator_traits<_InputIter>::difference_type
typename iterator_traits<_InputIter>::value_type
typename iterator_traits<_InputIter>::difference_type __n = 0;

difference_type, value_type就是依赖于_InputIter(模板类型参数)的类型名。源码如下:

template <class _Iterator>
struct iterator_traits {
  typedef typename _Iterator::iterator_category iterator_category;
  typedef typename _Iterator::value_type        value_type;
  typedef typename _Iterator::difference_type   difference_type;
  typedef typename _Iterator::pointer           pointer;
  typedef typename _Iterator::reference         reference;
};

内嵌是指定义在类名的定义中的。以上difference_type和value_type都是定义在iterator_traits中的。
依赖是指依赖于一个模板参数。typename iterator_traits<_InputIter>::difference_type中difference_type依赖于模板参数_InputIter。
类型名是指这里最终要指出的是个类型名,而不是变量。例如iterator_traits<_InputIter>::difference_type完全有可能是类iterator_traits<_InputIter>类里的一个static对象。

而且当我们这样写的时候,C++默认就是解释为一个变量的。所以,为了和变量区分,必须使用typename告诉编译器。

那么是不是所有的T::type_or_variable, 或者tmpl<T>:type_or_variable都需要使用typename呢?不是,有以下两个例外。

(3)不需要typename的模板中的类型

1)类模板定义中的基类列表
例如

template<class T>
class Derived: public Base<T>::XXX
{
...
}

2)类模板定义中的初始化列表

Derived(int x) : Base<T>::xxx(x)

...
}

因为编译器知道这里需要的是类型还是变量:1)基类继承列表里是类型,2)构造函数初始化列表里是成员变量





你可能感兴趣的:(C++中的一些关键字)