深入理解C++11
来源:互联网 发布:ping ip或域名 编辑:程序博客网 时间:2024/06/01 11:20
- 第2章 保证稳定性和兼容性
- finaloverride 控制
- 局部和匿名类型作模板实参
- 第3章 通用为本专用为末
- 继承构造函数
- 委派构造函数
- 右值引用移动语义和完美转发
- 移动语义
- 右值和左值的区别
- stdmove 强制转化为右值
- 完美转发
- 列表初始化
- 初始化列表
- POD类型
- 模板别名
- 第4章 新手易学老兵易用
- auto类型推导
- decltype
- decltype四规则
- 追踪返回类型
- 第5章 提高类型安全
- 强类型枚举
- C11的智能指针
- unique_ptr
- shared_ptr
- weak_ptr
- C与垃圾回收
- 第6章 提高性能及操作硬件的能力
- 常量表达式
- 常量表达式函数
- 变长模板
- 原子类型与原子操作
- 原子操作与C11原子类型
- 常量表达式
- 第7章 为改变思考方式而改变
- 指针空值 nullptr
- 默认函数的控制
- 类与默认函数
- default 与 delete
- lamda函数
- 第 8 章 融入实际应用
- 原生字符串字面量
整理 <<深入理解C++11 C++11新特性解析与应用>> 笔记
第2章 保证稳定性和兼容性
final/override 控制
struct Object { virtual void fun() = 0;}struct Base: public Object { void fun() final; // 声明为final}struct Derived: public Base { void fun(); // 无法通过编译}
struct Base { virtual void Turing() = 0; virtual void Dijkstra() = 0; virtual void VNeumann(int g) = 0; virtual void DKnurh() = 0; void Print();}struct DerivedMid: public Base { // void VNeumann(double g); // 接口被隔离,曾想多一个版本的VNeumann函数}struct DerivedTop: public DerivedMid { void Turing() override; void Dikjstra() override; // 无法通过编译, 拼写错误,非重载 void VNeumann(double g) override; // 无法通过编译, 参数不一致,非重载 void DKnuth() override; // 无法通过编译,常量性不一致,非重载 void Print() override; // 无法通过编译, 非虚函数重载}
局部和匿名类型作模板实参
template<typename T> class X {};template<typename T> void TempFun(T t) {}struct A{} a;struct { int i; } b; // 匿名类型typedef struct { int t; } B;void Fun() { struct C {} c; // 局部类型 X<A> x1; // c++98 通过, c++11 通过 X<B> x2; // c++98 错误, c++11 通过 X<C> x3; // c++98 错误, c++11 通过 TempFun(a); // c++98 通过, c++11 通过 TempFun(b); // c++98 错误, c++11 通过 TempFun(c); // c++98 错误, c++11 通过}
但是不能就地定义, 使用decltype获取类型
MyTemplate<struct { int a; } > t; // 编译错误,不能就地定义struct { int a;}A;MyTemplate<decltype(A)> t; // 编译通过
第3章 通用为本,专用为末
继承构造函数
情景: 在派生类中我们写的构造函数完完全全是为了构造基类,而基类有很多构造函数,那么我们需要为派生类写很多的”透传”构造函数. c++11 使用using声明继承基类的构造函数来解决这个重复写的问题.
struct A { A(int i) {} A(double d, int i) {} A(float f, int i, const char* c) {} // ...};struct B: A { using A::A; // 继承构造函数 // ... virtual void ExtraInterface () {}};
通过using A::A
的声明,把基类中的构造函数都继承到派生类B中.而且这是隐式声明的,意味这如果没有实际调用,编译器不会生成相关代码,节省了目标代码空间.
有的时候,会发生继承构造函数冲突问题,需要显示定义,阻止隐式生成相应的构造函数来解决冲突
struct A { A(int) {} };struct B { B(int) {} };struct C: A, B { using A::A; using B::B; C(int) {} // 显式定义}
注意:
1. 基类的构造函数是私有函数,不能继承
2. 使用了继承构造函数,那么不再为派生类生成默认构造函数.
委派构造函数
委派构造,就是指委派函数将构造的任务委派给委派构造函数(delegating constructor). 构造函数不能同时”委派”和使用初始化列表.初始化列表的初始化方式总是先于构造函数完成(实际在编译完成时已经决定了).
可以形成委派链,但是不能有委派环.
class Info { public: Info(): Info(1, 'a') {} Info(int i): Info(i, 'a') {} Info(char e): Info(1, e) {} private: Info(int i, char e): type(i), name(e) {} int type; char name;}
委派函数实际应用是使用构造模板函数产生目标构造函数, 委托构造函数使得泛型编程成为一种可能
#include <iostream>#include <deque>#include <vector>using namespace std;class TDConstructed { template<class T> TDConstructed(T first, T second): l(first, last) {} list<int> l; public: TDConstructed(vector<short>& v): TDConstructed(v.begin(), v.end()) {} TDConstructed(deque<int>& d): TDConstructed(d.begin(), d.end()) {}};
右值引用:移动语义和完美转发
移动语义
#include <iostream>using namespace std;class HasPtrMem { public: HasPtrMem(): d(new int(0)) { cout << "Construct: " << ++n_cstr << endl; } HasPtrMem(const HasPtrMem& h): d(new int(*h.d)) { cout << "Copy construct: " << ++n_cptr << endl; } ~HasPtrMem() { cout << "Destruct: " << ++n_dstr << endl; } int *d; static int n_cstr = 0; static int n_cptr = 0; static int n_dstr = 0;};HasPtrMen getTemp() { return HasPtrMen(); }int main() { HasPtrMen a = getTemp();}// c++ -std=c++11 test.cpp -fno-elide-constructors// 需要关掉编译器返回值优化选项(RVO, Return Value Optimization)
Construct: 1Copy construct: 1Destruct: 1Copy construct: 2Destruct: 2Destruct: 3
调用过程
graph TDGetTemp(( ))--构造-->HasPrtMemHasPrtMem--拷贝构造-->HasPtrMem临时对象HasPtrMem临时对象--拷贝构造-->a
添加移动构造函数
HasPtrMem(HasPtrMem&& h): d(h.d) { h.d = nullptr; // 将临时值的指针成员置空 cout << "Construct: " << ++n_mvtr << endl; }
右值和左值的区别
可以取地址,有名字的是左值.不能取地址的,没有名字的是右值.
在C++11程序中,所有值必须属于左值,纯右值,将亡值三者之一.右值分为:纯右值,将亡值
纯右值: 用于辨认临时变量和一些不跟对象关联的值,如 1 + 3 产生的临时变量值
将亡值: 是C++11新增的跟右值引用相关的表达式.如T&&的函数返回值.
T ReturnRvalue() {}T&& a = ReturnRvalue(); // 调用移动构造函数,为临时对象续命
注意: 右值引用不能绑定到左值上
int a;int&& b = a; // 编译失败
注意
常量左值引用是一个”万能”的存在,只读不能修改
T ReturnRvalue() {}T& e = ReturnRvalue(); // 编译失败const T& f = ReturnRvalue(); // 编译通过T&& g = ReturnRvalue(); // 编译通过
std::move 强制转化为右值
std::move
从实现上讲,基本等同与一个类型转换static_cast<T&&>(lvalue
std::move
转化的左值不会立刻被析构
#include <iostream>class Moveable { public: Moveable(): i(new int(3)) {} ~Moveable() { delete i; } Moveable(const Moveable& m): i(new int(*m.i)) {} Moveable(Moveable&& m): i(m.i) { m.i = nullptr; } int* i;};int main() { Moveable a; Moveable c(move(a)); // 调用移动构造函数 cout << *a.i << endl; // 运行错误}
move() 使用场景
在移动构造函数中,为了保证成员变量也能使用移动构造函数,需要用到move()
class Moveable { public: Moveable(Moveable&& m): i(m.i), h(move(m.h)) { m.i = nullptr; } int* i; HugeMem h;}
移动语义其他问题
Moveable(const Moveable &&);const Moveable ReturnVal();
上面的生命都会使得右值常量化,那么对临时变量的修改不能进行,无法实现移动语义.
在C++11中, 拷贝构造/赋值构造/移动构造/移动赋值 函数必须同时提供,或者同时不提供. 一旦提供一个,编译器将不会隐式生成其他三个.
对与移动语义,抛异常是危险的,因为可能移动语义未完成,异常抛出,导致一些指针成悬挂指针,因此尽量写不抛出异常的移动构造函数,一旦有异常,直接终止程序, 添加 noexcept
关键字
完美转发
#include <iostream>using namespace std;void RunCode(int & m) { cout << "lvalue ref"<< endl; }void RunCode(int && m) { cout << "rvalue ref"<< endl; }void RunCode(const int & m) { cout << "const lvalue ref"<< endl; }void RunCode(const int && m) { cout << "const rvalue ref"<< endl; }template<typename T>void PerfectForward(T&& t) { RunCode(forward<T>(t)); }int main() { int a; int b; const int c = 1; const int d = 0; PerfectForward(a); // lvalue ref PerfectForward(move(b)); // rvalue ref PerfectForward(c); // const lvalue ref PerfectForward(move(d)); // const rvalue ref}
列表初始化
初始化列表
#include <iostream>#include <map>using namespace std;int a[] = {1, 3, 5};map<int, float> d = {{1, 1.0}, {2, 2.0}, {5, 3.0}};int* i = new int(1);double* e = new double{1.2};int b[]{1, 3, 5};vector<int> c{1, 3, 5};
自定义的类使用列表初始化
#include <initializer_list>enum Gender { boy, girl };class People { public: People(initializer_list<pair<string, Gender> > l) { for (auto i = l.begin(); i != l.end(); i++) { data.push_back(*i); } } private: vector<pair<string, Gender> > data;};People ship2012 = {{"Garfield", boy}, {"HelloKitty", girl}};
应用
class Mydata { public: Mydata& operator[](initializer_list<int> l ) { for (auto i = l.begin(); i != l.end(); i++) { idx.push_back(*i); } return *this; } Mydata& operator=(int x) { if (idx.empty()) { return *this; } for (auto i = idx.begin(); i != idx.end(); ++i) { d.resize((*i > d.size()) ? *i : d.size()); d[*i - 1] = v; } idx.clear(); } return *this; private: vector<int> idx; vector<int> d;}int main() { Mydata d; d[{2, 3, 5}] = 7; d[{1, 4, 5, 8}] = 4; // d: 4 7 7 4 4 0 0 4}
POD类型
POD(Plain Old Data)的好处:
1. 字节赋值,安全的使用memset和memcpy对POD类型初始化
2. 对C内存布局兼容
3. 保证了静态初始化的安全有效
模板别名
在C++11中,using关键字的能力已经包括的typedef的部分
using uint = unsigned int;typedef unsigned int UINT;
在使用模板编程时,using的语法更灵活
template<typename T> using MapString = std::map<T, char*>;MapString<int> numberedString;// std::map<int, char*> numberedString;
第4章 新手易学,老兵易用
auto类型推导
使用细则
auto声明不带走常量性和易失性
double foo();float* bar();const auto a = foo(); // const doubleconst auto& b = foo(); // const double&volatile auto* c = foo(); // volatile float*auto d = a; // doubleauto& e = a; // const double&auto f = c; // float*volatile auto& g = c; // volatile float* &
decltype
int main() { int i; decltype(i) j = 0; cout << typeid(j).name() << endl; // int float a; double b; decltype(a + b) c; cout << typeid(c).name() << endl; // double}
decltype 以一个普通的表达式为参数,返回该表达式的类型.decltype类型推导在编译时进行.
decltype四规则
假设 e的类型是T
1. 如果e是一个没有带括号的标记表达式或者类成员访问表达式,那么decltype(e)就是e所命名的实体的类型.
2. 否则, 如果e是一个将亡值,那么decltype(e) 为 T&&
3. 否则, 如果e是一个左值,那么decltype(e) 为 T&
4. 否则, decltype为 T
只有1, 3容易搞混.单个标记符对应的表达式就是标记符表达式.如int arr[4]
中的arr
, 而arr[3]
就不是
int i;decltype(i) a; // int 规则一decltype((i)) b; // int& 规则三, 编译错误
decltype能带走const和volatile关键字
int* p;decltype(p)* a; // int**int i = 1;int&j = i;decltype(j)& var2 = i; // int&, 冗余的&, 忽略
追踪返回类型
template<typename T1, typename T2>auto Sum(T1& t1, T2& t2) -> decltype(t1 + t2) { return t1 + t2;}
复合符号 -> decltype(t1 + t2)
称为追踪返回类型
第5章 提高类型安全
强类型枚举
强类型枚举不会将名字输出到全局空间, 引用时必须加上名字空间才可以.
enum class Type { General, Light, Medium, Heavy};
C++11的智能指针
unique_ptr
不能与其他unique_ptr
类型的指针对象共享所指对象的内存,只能通过move
函数将”所有权”转移.转移后,原来的unique_ptr
不能在用.
事实上,unique_ptr
是一个删除了拷贝构造函数,保留了移动构造函数的指针封装类型.
shared_ptr
只有在引用计数归零的时候,share_ptr才会真正释放所占有的堆内存空间
weak_ptr
可以指向shared_ptr
指针指向的对象内存,但是不拥有该内存.在使用lock
成员时,返回一个指向内存的shared_ptr
对象,且在所指内存无效时返回nullptr
.可以验证shared_ptr
智能指针的有效性.
#include <iostream>#include <memory>using namespace std;void check(weak_ptr<int>& wp) { shared_ptr<int> sp = wp.lock(); if (sp != nullptr) cout << "still " << *sp << endl; else cout << "pointer is invalid." << endl;}int main() { unique_ptr<int> up1(new int(11)); unique_ptr<int> up2 = up1; // 编译错误 cout << *up1 << endl; // 11 unique_ptr<int> up3 = move(up1); // 现在p3是数据唯一的unique_ptr 指针 cout << *up3 << endl; // 11 cout << *up1 << endl; // 运行时错误 up3.reset(); up1.reset(); // 不会导致运行时错误 cout << *up3; // 运行时错误 shared_ptr<int> sp1(new int(22)); shared_ptr<int> sp2 = sp1; cout << *sp1 << endl; // 22 cout << *sp2 << endl; // 22 sp1.reset(); cout << *sp2 << endl; // 22 weak_ptr<int> wp = sp2; check(wp); // still 22 sp2.reset(); check(wp); // pointer is invlid return 0;}
C++与垃圾回收
第三方库Boehm 支持标记-清楚方法的垃圾回收.需要使用库的堆内存分配函数显式代替malloc,继而将堆内存的管理交给垃圾回收器来完成垃圾回收.不过由于C/C++中指针类型的使用十分灵活,这样的库在实际使用中会有一些限制,可移植性也不好.
第6章 提高性能及操作硬件的能力
常量表达式
const
是运行时常量, constexpr
是编译时常量
#include <iostream>using namespace std;const int getConst() { return 1; }constexpr int getConstexpr() { return 2; }int main() { int input; cin >> input; switch(input) { case getConst(): // 编译失败 break; case getConstexpr(): // 编译通过 break; default: break; } return 0;}
常量表达式函数
constexpr int getConstexpr() { return 2; }
要求:
1. 函数体只有单一的return语句,不能有其他语句
2. 函数必须有返回值,不能是void函数
3. 使用前定义
4. return返回语句表达式不能使用非常量表达式,全局数据,且必须是一个常量表达式
最明显的限制是第一条:要求函数体只有一条语句, 且该语句必须是return语句,意味着
constexpr int getConstexpr() { const int i = 2; return 2;}
是编译不通过的.
不过, 一切不会产生实际代码的语句在常量表达式函数中是可以使用的.
constexpr int getConstexpr() { static_assert(0 == 0, "assert fail."); return 2;}
此外, 如using, typedef等命令也能通过编译
此外, 一些危险操作,比如赋值操作在常量表达式函数中是不允许的
constexpr int getConstexpr(int x) { return x = 1; }
变长模板
[todo]
原子类型与原子操作
原子操作与C++11原子类型
#include <atomic>#include <iostream>#include <thread>using namespace std;atomic_llong total{0};void func(int) { for (long long i = 0; i < 100000000LL; i++) { total += i; }}int main() { thread t1(func, 0); thread t2(func, 0); t1.join(); t2.join(); cout << total << endl; // 9999999900000000 return 0;}
使用了原子类型atomic_llong
可以保证数据的原子访问.其他内置类型的原子类型定义
还可以自定义原子类型 std::atomic<T>
, 对于原子类型,智能从模板参数类型中进行构造,不允许拷贝构造,移动构造,赋值构造.
atomic<float> af{1.2f} // 编译成功atomic<float> af = 1.2f // 编译失败atomic<float> af1{af} // 编译失败atomic<float> af1 = af; // 编译失败
从std::atomic<T>
到T
可以直接赋值
atomic<float> af{1.2f}float f = af
第7章 为改变思考方式而改变
指针空值 — nullptr
nullptr
的数据类型是nullptr_t
typedef decltype(nullptr) nullptr_t;
特点:
1. nullptr_t
类型数据可以隐式转换成任意一个指针类型
2. nullptr_t
不能转化成非指针类型
3. nullptr_t
不适用于算数运算表达式
4. nullptr_t
只能和指针类型进行比较
默认函数的控制
默认函数有:
1. 构造函数
2. 拷贝构造函数
3. 拷贝赋值函数(operator=)
4. 移动构造函数
5. 移动拷贝函数
6. 析构函数
此外,C++为自定义类型提供全局默认操作符函数
1. operator,
2. operator &
3. operator &&
4. operator *
5. operator->
6. operator->*
7. operator new
8. operator delete
类与默认函数
class NoCopyCator {public: NoCopyCator() = default; NoCopyCator(const NoCopyCator&) = delete; // 有效阻止用户错用拷贝构造函数};}
default 与 delete
class ConvType{public: ConvType(int i) {} // 删除 char 版本 ConvType(char c) = delete;};void func(ConvType c) {}int main() { func(3); func('a'); // 编译失败 ConvType c1(3); ConvType c2('a'); // 编译失败}
delete
不要与explicit
连用
class ConvType{public: ConvType(int i) {} explicit ConvType(char c) = delete; // `delete`不要与`explicit`连用};void func(ConvType c) {}int main() { func(3); func('a'); // 编译通过,调用 int 版本 ConvType c1(3); ConvType c2('a'); // 编译失败}
可以显式删除operator new 函数来避免堆上分配对象
class NoHeapAlloc {public: void* operator new(std::size_t) = delete;};int main() { NoHeapAlloc all; NoHeapAlloc* p = new NoHeapAlloc; // 编译失败 return 0;}
如果想限制对象在栈上的存储,可以删除析构函数,这样对象可以在堆中存储,不能在栈上或者静态构造
lamda函数
语法定义 [capture](parameters) mutable -> return-type { statement }
其中
1. captrue
: 捕捉列表, [var]值传递,[=]捕捉父作用域的所有变量(值传递, 包括this), [&var]引用捕捉var, [&]引用捕捉父作用域所有变量, [=, &a, &b]表示a, b引用捕捉,其他值传递.
2. parameters
: 函数列表
3. mutable
: 默认情况下,lamda函数是const函数,mutable可以取消其常量性
4. return-type
:返回值,采用追踪返回类形式声明函数的返回类型,在返回类型明确时,可以省略
5. statement
:函数体
现有的C++11标准中lamda等价与有常量的仿函数
[val]() { val = 3; // 编译失败}class const_val_lamda {public: const_val_lamda(int v): val(v) {} void operator()() const { val = 3; } // 编译失败private: int val;}
第 8 章 融入实际应用
原生字符串字面量
#include <iostream>using namespace std;int main() { cout <<R"(hello, \n world)"<< endl; return 0;}
输出
hello, \n world
- C++:深入理解sizeof
- 深入理解extern "C"
- 深入理解extern "C"
- 深入理解C语言
- 深入理解extern "C"
- 深入理解 c语言
- 深入理解C语言
- 深入理解C语言
- 深入理解C语言
- 深入理解c语言
- 深入理解C语言
- 深入理解C语言
- 深入理解C语言
- 深入理解C语言
- 深入理解C语言
- 深入理解C语言
- 深入理解C语言
- 深入理解C语言
- 别踩白块
- 记一次投票项目
- 数据挖掘:概念与技术(第三版)之第十一章的学习记录
- 武汉注册公司需要的注册资料
- 字符串和数组 零碎知识点及注意事项
- 深入理解C++11
- ORACLE 常用单行函数
- Map按照key排序(升序,降序)---String
- LR 的上传文件与下载文件
- 接口测试工具抉择
- Thinkphp实现文件上传与删除
- android studio 文件编码格式乱码
- Android6.0运行时权限封装(避免用户选择不再提示后无法获取权限的问题)
- jfinal config包的主要类