C++11中对类(class)新增的特性

default/delete 控制默认函数

在我们没有显式定义类的复制构造函数和赋值操作符的情况下,编译器会为我们生成默认的这两个函数:
默认的赋值函数以内存复制的形式完成对象的复制。
这种机制可以为我们节省很多编写复制构造函数和赋值操作符的时间,但是在某些情况下,比如我们不希望对象被复制,
在之前我们需要将复制构造函数和赋值操作符声明为private,现在可以使用delete关键字实现:

class X {
// …
X& operator=(const X&) = delete; // 禁用类的赋值操作符
X(const X&) = delete;
};

显式地使用default关键字声明使用类的默认行为,对于编译器来说明显是多余的,但是对于代码的阅读者来说,使用default显式地定义复制操作,则意味着这个复制操作就是一个普通的默认的复制操作。

override /final 强制重写/禁止重写虚函数

派生类中可以不实现基类虚函数,也可以实现,但不使用virtual关键字;

这很容易给人造成混淆,有时为了确认某个函数是否是虚函数,我们不得不追溯到基类查看;

C++11引入了两个新的标识符: override和final

override,表示函数应当重写基类中的虚函数。(用于派生类的虚函数中)

final,表示派生类不应当重写这个虚函数。(用于基类中)

struct B {
virtual void f();
virtual void g() const;
virtual void h(char);
void k(); // non-virtual
virtual void m() final;
}; struct D : B {
void f() override; // OK: 重写 B::f()
void g() override; // error: 不同的函数声明,不能重写
virtual void h(char); // 重写 B::h( char ); 可能会有警告
void k() override; // error: B::k() 不是虚函数
virtual void m(); // error: m()在基类中声明禁止重写
};

有了这对兄弟,我们的虚函数用起来更为安全,也更好阅读;

委托构造函数 Delegating constructors

在C++98中,如果你想让两个构造函数完成相似的事情,可以写两个大段代码相同的构造函数,或者是另外定义一个init()函数,让两个构造函数都调用这个init()函数。例如:

class X {
int a;
// 实现一个初始化函数
validate(int x) {
if (0<x && x<=max) a=x; else throw bad_X(x);
}
public:
// 三个构造函数都调用validate(),完成初始化工作
X(int x) { validate(x); }
X() { validate(42); }
X(string s) {
int x = lexical_cast<int>(s); validate(x);
}
// …
};

这样的实现方式重复罗嗦,并且容易出错。

在C++0x中,我们可以在定义一个构造函数时调用另外一个构造函数:

class X {
int a;
public:
X(int x) { if (0<x && x<=max) a=x; else throw bad_X(x); }
// 构造函数X()调用构造函数X(int x)
X() :X{42} { }
// 构造函数X(string s)调用构造函数X(int x)
X(string s) :X{lexical_cast<int>(s)} { }
// …
};

继承的构造函数 Inheriting constructors

C++11提供了将构造函数晋级的能力:

比如以下这个示例,基类提供一个带参数的构造函数,而派生类没有提供;

如果直接使用D1 d(6);将会报错;通过将基类构造函数晋级,派生类中会隐式声明构造函数D1(int);

需要注意的是,晋级后的基类构造函数是无法初始化派生类的成员变量的,所以如果派生类中有成员变量,

需要使用初始化列表初始化;

struct B1 {
B1(int) { }
};
struct D1 : B1 {
using B1::B1; // 隐式声明构造函数D1(int)
// 注意:在声明的时候x变量已经被初始化
int x{0};
};
void test()
{
D1 d(6); // d.x的值是0
}

类内部成员的初始化 Non-static data member initializers

在C++98标准里,只有static const声明的整型成员能在类内部初始化,并且初始化值必须是常量表达式。这些限制确保了初始化操作可以在编译时期进行。

class X {
static const int m1 = 7; // 正确
const int m2 = 7; // 错误:无static
static int m3 = 7; // 错误:无const
static const string m5 = “odd”; //错误:非整型
};

C++11的基本思想是,允许非静态(non-static)数据成员在其声明处(在其所属类内部)进行初始化。这样,在运行时,需要初始值时构造函数可以使用这个初始值。现在,我们可以这么写:

class A {
public:
int a = 7;
};
它等同于使用初始化列表:
class A {
public:
int a;
A() : a(7) {}
};

单纯从代码来看,这样只是省去了一些文字输入,但在有多个构造函数的类中,其好处就很明显了:

class A {
public:
A(): a(7), b(5), hash_algorithm(“MD5″),
s(“Constructor run”) {}
A(int a_val) :
a(a_val), b(5), hash_algorithm(“MD5″),
s(“Constructor run”)
{}
A(D d) : a(7), b(g(d)),
hash_algorithm(“MD5″), s(“Constructor run”)
{}
int a, b;
private:
// 哈希加密函数可应用于类A的所有实例
HashingFunction hash_algorithm;
std::string s; // 用以指明对象正处于生命周期内何种状态的字符串
};

可以简化为:

class A {
public:
A() {}
A(int a_val) : a(a_val) {}
A(D d) : b(g(d)) {}
int a = 7;
int b = 5;
private:
//哈希加密函数可应用于类A的所有实例
HashingFunction hash_algorithm{“MD5″};
//用以指明对象正处于生命周期内何种状态的字符串
std::string s{“Constructor run”};

多么优雅!

移动构造和移动赋值

在C++98中,我们自定义的类,会默认生成拷贝赋值操作符函数和拷贝赋值函数以及析构函数;

在C++11中,依赖于新增的move语义,默认生成的函数多了2个移动相关的:移动赋值操作符( move assignment )和移动构造函数( move constructor );

BS建议,如果你显式声明了上述 5 个函数或操作符中的任何一个,你必须考虑其余的 4 个,并且显式地定义你需要的操作,或者使用这个操作的默认行为。

一旦我们显式地指明( 声明 , 定义 , =default , 或者 =delete )了上述五个函数之中的任意一个,编译器将不会默认自动生成move操作。

一旦我们显式地指明( 声明 , 定义 , =default , 或者 =delete )了上述五个函数之中的任意一个,编译器将默认自动生成所有的拷贝操作。但是,我们应该尽量避免这种情况的发生,不要依赖于编译器的默认动作。

如果你声明了上述 5 个默认函数中的任何一个,强烈建议你显式地声明所有这 5 个默认函数。例如:

template<class T>
class Handle {
T* p;
public:
Handle(T* pp) : p{pp} {}
// 用户定义构造函数: 没有隐式的拷贝和移动操作
~Handle() { delete p; }
Handle(Handle&& h) :p{h.p}
{ h.p=nullptr; }; // transfer ownership
Handle& operator=(Handle&& h)
{ delete p; p=h.p; h.p=nullptr; } // 传递所有权
Handle(const Handle&) = delete; // 禁用拷贝构造函数
Handle& operator=(const Handle&) = delete;
// ...
};

参考

http://www.stroustrup.com/C++11FAQ.html

https://www.chenlq.net/books/cpp11-faq

Posted by: 大CC | 02SEP,2015

博客:blog.me115.com [订阅]

Github:大CC

C++11中对类(class)新增的特性的更多相关文章

  1. 关于c++11中static类对象构造函数线程安全的验证

    在c++11中,static静态类对象在执行构造函数进行初始化的过程是线程安全的,有了这个特征,我们可以自己动手轻松的实现单例类,关于如何实现线程安全的单例类,请查看c++:自己动手实现线程安全的c+ ...

  2. callable object与新增的function相关 C++11中万能的可调用类型声明std::function<...>

    在c++11中,一个callable object(可调用对象)可以是函数指针.lambda表达式.重载()的某类对象.bind包裹的某对象等等,有时需要统一管理一些这几类对象,新增的function ...

  3. 22.编写一个类A,该类创建的对象可以调用方法showA输出小写的英文字母表。然后再编写一个A类的子类B,子类B创建的对象不仅可以调用方法showA输出小写的英文字母表,而且可以调用子类新增的方法showB输出大写的英文字母表。最后编写主类C,在主类的main方法 中测试类A与类B。

    22.编写一个类A,该类创建的对象可以调用方法showA输出小写的英文字母表.然后再编写一个A类的子类B,子类B创建的对象不仅可以调用方法showA输出小写的英文字母表,而且可以调用子类新增的方法sh ...

  4. 一起学习c++11——c++11中的新增的容器

    c++11新增的容器1:array array最早是在boost中出现:http://www.boost.org/doc/libs/1_61_0/doc/html/array.html 当时的初衷是希 ...

  5. C++中的类继承(2)派生类的默认成员函数

    在继承关系里面, 在派生类中如果没有显示定义这六个成员 函数, 编译系统则会默认合成这六个默认的成员函数. 构造函数. 调用关系先看一段代码: class Base { public : Base() ...

  6. C++11 中function和bind以及lambda 表达式的用法

    关于std::function 的用法:  其实就可以理解成函数指针 1. 保存自由函数 void printA(int a) { cout<<a<<endl; } std:: ...

  7. C++ 11中几个我比较喜欢的语法(三)

    随着Vsisual Studio 2013 RC版的放出,之前承诺的对C++ 11语法支持已经全部完成,本文是C++ 11中我喜欢的语法系列的最后一部分(一),(二). 非静态成员直接初始化 在C++ ...

  8. 对C++11中的`移动语义`与`右值引用`的介绍与讨论

    本文主要介绍了C++11中的移动语义与右值引用, 并且对其中的一些坑做了深入的讨论. 在正式介绍这部分内容之前, 我们先介绍一下rule of three/five原则, 与copy-and-swap ...

  9. C++ primer plus读书笔记——第11章 使用类

    第11章 使用类 1. 运算符重载是一种形式的C++多态. 2. 不要返回指向局部变量或临时对象的引用.函数执行完毕后,局部变量和临时对象将消失,引用将指向不存在的数据. 3. 运算符重载的格式如下: ...

随机推荐

  1. IOS第三天

    第三天 ******** 九宫格代码的现实 @interface HMViewController () /** 应用程序列表 */ @property (nonatomic, strong) NSA ...

  2. Linux内存管理基本概念

    1. 基本概念 1.1 地址 (1)逻辑地址:指由程序产生的与段相关的偏移地址部分.在C语言指针中,读取指针变量本身值(&操作),实际上这个值就是逻辑地址,它是相对于你当前进程数据段的地址.( ...

  3. 读过的laravel文章

    Laravel 中使用 JWT(Json Web Token) 实现基于API的用户认证 http://www.tuicool.com/articles/IRJnaa api token https: ...

  4. 详解依赖注入(DI)和Ioc容器

    简单的来说,关键技术就是:注册器模式. 场景需求 我们知道写一个类的时候,类本身是有个目的的,类里面有很多方法,每个方法搞定一些事情:我们叫这个类为主类. 另外这个主类会依赖一些其他类的帮忙,我们叫这 ...

  5. 通过php获取用户微信openid

    // 基于CI框架 // 访问开始页面 public function url() { // wxAction/oauth2 微信回调地址:微信传入code值,通过该code在wxAction/oau ...

  6. Codeigniter2.25部署Linux(php5.6)

    1).默认路由:修改system/core/Router.php 中第146行.如下图所示.ps:转换成小写我也是醉了...注释的代表是codeigniter作者写的,而上面的是我更改的 2).mod ...

  7. MVC过滤器详解 面向切面编程(AOP)

    面向切面编程:Aspect Oriented Programming(AOP),面向切面编程,是一个比较热门的话题.AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个 ...

  8. jQuery源代码学习之四——jQuery.callbacks

    自己实现的callbacks模块相较于jquery源代码中的callbacks模块有所简化,表面上看没有考虑firing这个参数,没有对之进行任何处理,即没有考虑在函数执行过程中,再次调用add,re ...

  9. Lua JSONRPC学习笔记

    JSON RPC JSON RPC 为利用json数据格式来执行远程调用方式, 作用同xmlrpc,不过与xmlrpc相比, jsonrpc更加轻量,json更加节省数据量,更加可读性高. 官网网站: ...

  10. DNS分别在什么情况下使用UDP和TCP

    DNS同时占用UDP和TCP端口53是公认的,这种单个应用协议同时使用两种传输协议的情况在TCP/IP栈也算是个另类.但很少有人知道DNS分别在什么情况下使用这两种协议.     如果用wiresha ...