Home

cuipf blog

27 Aug 2017

C++11特性介绍——类型与关键字相关

非静态成员的sizeof

C++ 98对非静态成员变量使用sizeof是不能通过编译的,而在C++ 11中,对于非静态成员使用sizeof完全合法的。

/*
*	非静态成员的sizeof
*/

struct People
{
public:
	int hand;
	static People* all;
};

void test_size_of()
{
	People p;
	cout << sizeof(p.hand) << endl;	       //C++98 通过 C++11 通过
	cout << sizeof(People::all) << endl;   //C++98 通过 C++11 通过
	cout << sizeof(People::hand) << endl;  //C++98 错误 C++11 通过
}

如果在C++ 98 中想要获取一个没有定义类实例的类成员大小的时候,可以采用下面的方式:

//C++98中获取没有类实例类的非静态成员大小的方法
cout << sizeof(((People*)0)->hand) << endl;

扩展friend的语法

回忆friend在c++中的使用,声明类的友元,友元可以无视类中成员的属性,无论成员是public,protected,或者是private,友元函数和友元类都可以访问,friend多少有点破坏了编程中的封装性,但是friend还在很多程序中被用到。

C++11 对friend做了一些改进,以使其更好用:

/*
*	friend的扩展使用
*/

class FriendTest;
typedef FriendTest FT;
class Expend1
{
	friend class FriendTest;    //C++98 通过 C++11 通过
};
class Expend2
{
	friend FriendTest;	        //C++98 失败 C++11 通过
};
class Expend3
{
	friend FT;				    //C++98 失败 C++11 通过
};

//friend可以为类模板声明友元
template <typename T>
class Expend4
{
	friend T;
};

Expend4<FriendTest> ef;		   //类型FriendTest在这里是Expend4的友元
Expend4<int> pi;			   //int类型模板参数,友元声明被忽略

final和override控制

final

问题:

基类A中成员函数fun声明为virtual,那么对于其派生类B,fun总是被重载的(除非被重写了),但有时,我们并不想再B中重载fun,如何解决, C++ 98明显很无奈!

解决:

C++ 11 中引入了final关键字来阻止函数继续被重写,final的作用是使派生类不可覆盖它所修饰的虚函数。

代码:

/*
*	c++11中final的使用
*/

class Object
{
public:
	virtual void func() = 0;
};

class Base : public Object
{
	virtual void func() final;  
};

class Derived : public Base
{
//	void func(); //无法通过编译        
};

说明:

一般final只用于派生类中,用于阻止一个接口可重载性,当然final也可以用于base类,但是这样显然是无意义的,不如直接不适用virtual岂不是更好。

override

问题:

都知道在基类声明了virtual的函数,派生类继承该函数virtual可以写也可以不写;但是在跨层继承和多重继承这样的代码就会难于阅读, 是否重载了一个接口,接口名字是否错误,都会难于检测。

解决:

C++ 11中,为了帮助程序员写复杂继承结构,引入了虚函数描述符override,如果派生类在虚函数中声明时使用了override描述符,那么该函数必须重载其基类中的同名函数。 否则代码无法编译。

代码:

struct Base1
{
	virtual void Turing() = 0;
	virtual void VNeumann(int g) = 0;
};

struct DerivedMid: public Base1
{
	void Turing() override;	     //
//	void VNeumann() override;	 //无法通过编译 参数不一样
	void VNeumann(int g) override;
};

noexcept修饰符和noexcept操作符

noexcept形如其名地,表示其修饰的函数不会抛出异常。不过与throw动态异常声明不同的是,在C++11中,noexcept修饰的函数抛出了异常,编译器会直接调std::terminate()函数来终止程序的运行;

C++11中使用noexcept两种形式:

void excpt_func() noexcept;
//常量表达式的结果会被转换成bool类型的值,如果为true,表示函数不会抛出异常,反之可能抛出
void excpt_func() noexcept(常量表达式)  

noexcept作为一个操作符,通常可以用于模板。比如:

template <class T>
void func() noexcept(noexcept(T())) {}

说明:

  1. 函数func是否是一个noexcept的函数,将由T()表达是是否会抛出异常决定。
  2. 这里的第二个noexcept就是一个noexcept操作。其参数是一个有可能抛出异常的表达式,其返回值为false,反之为true;

强类型枚举

声明如下:

enum class Typechar
{
	General,
	Light,
	Medium,
	Heavy
};

优点:

  1. 强作用域,强类型枚举成员的名称不会被输出到其父作用域空间;
  2. 转换限制,强类型枚举成员不可以与整型隐式地相互转换;
  3. 可以指定底层类型,强类型枚举默认底层类型为int,但也可以显示地指定底层类型, 如上就保存为char类型;

POD类型

POD:Plain Old Data的缩写; C++11将POD划分为两个基本概念的集合, 即:平凡的(trivial)和标准布局的(standard layout);

  • 平凡的类或结构体
    1. 拥有默认的构造函数和析构函数;
    2. 拥有trivial copy constructor 和 trivial move constuctor;
    3. 拥有trivial assignment operator和trivial move operator;
    4. 不包含虚函数和虚基类;
  • 标准布局的类或结构体
    1. 所有非静态成员有相同的访问权限(public,private, protected);
    2. 派生类中有非静态成员,且只有一个仅包含静态成员的基类;
    3. 基类有非静态成员,派生类没有非静态成员;
    4. 类中第一个非静态成员的类型与其基类不同;
    5. 没有虚函数和虚基类;
    6. 所有非静态数据成员均符合标准布局类型,其基类也符合标准布局;

C++11提供的判断方式如下:

//判断类型T是否是一个平凡的类型
template <typename T> struct std::is_trivial;
//是否是一个标准布局的类型
template <typename T> struct std::is_standard_layout
//是否是一个POD类型
template <typename T> struct std::is_pod

POD优点

  • 字节赋值,代码中可以安全的使用memset和memcpy对POD类型进行初始化和拷贝等操作;
  • 提供对C内存布局的兼容。C++ 和C可以进行相互操作,POD类型在C和C++间操作总是安全的;
  • 保证静态初始化的安全有效;静态初始化能够提高性能,POD类型的对象初始化往往更加简单。

非限制联合体

  • C++ 98中规定, 联合体(Union)不允许拥有非POD类型成员, 也不允许拥有静态或者引用类型的成员;
  • C++ 11中, 逐渐放开了对联合体数据成员的限制, 规定任何非引用类型都可以成为联合体的成员; 这样的联合体, 即所谓的非限制联合体.
  • 不过在实践中我们发现, C++ 11中, 联合体不允许存在静态的数据成员存在;

cuipf

scribble