继承构造函数
- 如果派生类并不需要改变构造函数,那么可以用新的
using Base::Base
直接继承基类的构造函数,基类构造函数很多的时候,这样做非常方便。如果派生类还有自己的成员需要初始化,可以利用类成员初始化表达。
class A{
A(int a):a(a){}
int a;
};
class B:A
{
using A::A
}
- 使用继承构造函数,编译器不会产生真正的目标代码,可以节约目标代码控件。
- 继承构造函数参数的默认值不会被继承,而且会产生多个继承构造函数。
A(int a = 1,double = 2)
的继承类使用using A::A
会产生B(int,double);B(int);B();B(const B &);
多个构造函数。因此,如果父类有默认参数的构造函数,使用继承构造需要很小心。
委派构造函数
- 在自己的构造函数里调用自己另外的构造函数,以便减少重复代码。
class A{
A(){init();}
A(int a):{init();this->a = a;}
A(char c):{init();this->c = c;}
};
- 使用tricky一点的方法是利用placement new
class A{
A(){init();}
A(int a):{ new(this)A();this->a = a;}
A(char c):{new(this)A();this->c = c;}
};
- 在C++11中使用委派构造函数
class A{
A(){init();}
A(int a):A(){ this->a = a;}
A(char c):A(){this->c = c;}
};
- 区别调用者和被调用者,A(int a)被称为 委派构造函数(delegate constructor),A()被称为 目标构造函数(target constructor)。
- 委派构造函数和初始化列表不能同时使用。比如
A(int):A(),a(a){}
是非法的,初始化代码需要放到函数体。 - 委派构造函数互相调用会形成链状,但是不能成环,会导致编译错误。
移动语义和完美转发
- 移动语义:利用右值临时变量对变量进行拷贝构造时,变量偷走有时右值变量构造函数里创建的堆上的资源。右值临时变量是即将消失的变量,拿走堆上的资源不会产生问题,减少堆的拷贝。
class A{
A(){ d = new Data;}
A(const A & a):d(new Data(a.d)){}//普通拷贝
A(A && a):d(a.d){ a.d = nullptr; }//右值引用的拷贝
Data * d;
}
- 左值:可以取地址的值,有名字的值。
int a; &a
合法,左值 - 右值:不能取地址的值,没有名字的值。
int b,c; &(b+c)
非法,(b+c)是临时变量,是右值。右值由两个概念构成:将亡值(xvalue,eXpiring value). 和 纯右值(prvalue, pure Rvalue)。2, true, ‘c’,lambda表达式,类型转换返回值都是右值。 返回右值引用的表达式 T&& , std::move的返回值,转换为T&& 类型的值 都是右值。C++11所有的值必然是 左值,将亡值,纯右值之一。 - 右值引用为了区别C++98里的引用,将98里称为左值引用,左值引用是有名字变量的引用,右值引用是匿名变量的引用。
T getValue(){ T t; return t;}
getValue(); //返回值临时变量立马被析构
T && a = getValue(); //右值引用临时变量,临时变量暂时不会被析构
const T & a = getValue(); //有效,常量左值引用可以引用临时变量
T & a = getValue(); //非法,非常量左值引用不能引用右值
- 因此,右值引用有延长临时变量生命周期的作用,常量左值应用也能延长临时变量生命周期,但是不能更改。
- 标准库
<type_traits>
提供三个模板类,可以判断是否是左值引用还是右值引用、引用类型。
is_rvalue_reference,is_lvalue_reference,is_reference
cout<<is_rvalue_reference<string &&>::value;
std::move - C++11 在
<utility>
里提供强制转换函数,从实现上将,基本等同于一个类型转换函数static_cast<T&&>(lvalue)
T t1 = std::move(t2)
,如果T没有实现移动构造,将会调用拷贝构造函数。- C++11中拷贝/移动构造有三个版本
T(const T &);T(T&);T(T&&)
移动构造一般要用来偷数据,因此默认没有const版本。 move_if_noexcept(t)
:如果t的移动构造有noexcept修饰,则返回右值,没有noexcept修饰则返回左值,调用拷贝构造。- RVO/NRVO (return value optimization) 优化,是g++/clang的编译选项,有时会优化掉构造和移动,若要正确实验移动构造的一些demo时,就关掉这个选项。
完美转发 - 函数模板中,完全按照模板的参数类型,将参数传递给函数内另一个函数。由于内部函数接收的参数可能是const、右值引用等,就得需要实现多个版本的模板,为了解决这个这个问题,C++11引入引用折叠。
C++11之前可能需要这样的处理
void inner(const char *);
template<class T>
void iamforwarding(const T &t){inner(t);}
void iamforwarding(T & t){inner(t);}
void iamforwarding(T && t){inner(t)}
- 引用折叠(reference collapsing)
typedef const int T;
typedef T & TR;
TR & v = 1; //c++98编译错误,c++11引用折叠
折叠规则:只要定义中出现左值,就是左值。
因此可以如此书写
template<class T>
void iamforwarding(T && t)
{
inner(static_cast<T &&>(t));
}
这里如果实参是X的左值引用,T推导为X&, T&&推导为 X& &&,折叠后结果还是X&. 而如果是X的右值引用,T&&推导为 X&& &&, 结果是X&&。因此实现了完美转发。注意记录右值引用的变量本身确实左值,即int && a = 2;
a本身是左值,作为参数调用函数时是会调用左值的版本,因此inner
需要把t转化为右值。
- 完美转发中static_cast
template<class T>
void iamforwarding(T && t)
{
inner(forward(t));
}
显示转换操作符
- 构造函数前加上explicit,可以禁止隐式类型转换。而在C++11里,explicit的使用范围扩大到自定义类型转换。
template<class T>
class Ptr{
Ptr(T * p):p(p){}
bool operator() const {return p!= nullptr;}
T * p;
}
这里加入bool 类型转换后,完美可以直接使用if(p){}
,但同时也让p1+p2
在语法上成立,为了避免这种隐式转换,加上explicit
explicit bool operator() const {return p!= nullptr;}
初始化列表
- c++98里可以对数组用花括号统一列表初始化,但在vector等集合里的数据就没法通过这样的方式初始化,c++11允许了这样的初始化方式,称为初始化列表(initializer list)。花括号前加不加等号 效果都相同。
int a[]{1,2,3}; int a[] = {1,2,3}
- 使用initializer_list模板来自定义初始化列表的初始化逻辑。在头文件
<initializer_list>
内定义。
class A{
A(initializer_list<pair<char c,int i>> l){
for(auto it = l.begin(); it != l.end(); ++it){
data.push_back(*it);
}
}
vector<pair<char,int>> data;
}
- 初始化列表能够防止类型收窄(会产生数据变化或精度丢失的隐式转化),产生类型收窄时会编译错误
int a{2.0}//收窄,
char a = 127;//收窄,可以编译通过。
char b{127};//收窄,不能编译通过
POD (Plain Old Data)
- 完全和C兼容,可以使用memcpy和memset设置的数据。
- 包含两个概念,平凡的(trivial)和标准布局(standard layout).详细介绍参考Trivial, standard-layout, and POD types
trivial 平凡的要求:
- no virtual functions or virtual base classes
- no base classes with a corresponding non-trivial constructor/operator/destructor
- no data members of class type with a corresponding non-trivial constructor/operator/destructor
如果有自定义的构造函数,那么必须显示提供default版本的构造函数。(个人感觉就是保证没有虚表指针,内存里都是具体数据,不清楚自定义构造/拷贝/操作符 函数和缺省函数对数据layout的影响)
trivial保证了数据连续分布,因为有对齐操作,可能数据之间有padding,能保证C++里数据之间的内存拷贝,但是不能保证和C里是一致的。
standart layout 标准布局的要求
- no virtual functions or virtual base classes
- all non-static data members have the same access control
- all non-static members of class type are standard-layout
- any base classes are standard-layout has no base classes of the same type as the first non-static data member.
- meets one of these conditions:
- no non-static data member in the most-derived class and no more than one base class with non-static data members, or
- has no base classes with non-static data members
- 基类和派生类只有一个有非静态成员
- 访问权限要相同(public,private,protected)
- 第一个成员不能是基类类型。
struct B{};
struct D1:B{//非标准类型
B b;
}d1;
struct D2:B{标准类型
int a;
B b;
}d2;
这条规则是基于C++允许优化不包含成员的基类产生的。在C++标准中,当基类没有成员时,标准允许派生类第一个成员和基类共享地址&d2 == &d2.a
,可是当第一个成员和基类类型相同时,C++标准要求类型相同的对象必须地址不同,于是编译器仍然会给基类分配一个字节空间long long(&d1) + 1 == long long(&d1.b)
POD要求同时满足trivial和standard layout
可以使用辅助类判断是否是trivial,standard layout或pod
template<typename T>
struct std::is_trivial;
template<typename T>
struct std::is_standart_layout;
template<typename T>
struct std::is_pod;
pod的好处
1. 字节赋值,安全使用memset和memcpy对pod类型初始化和拷贝
2. 提供对C兼容,数据在c和c++之间操作总是安全的
3. 保证静态初始化的安全有效。pod类型对象初始化可以放入目标文件.bss段,在初始化中直接赋值为0
非受限联合体(unrestricted union)
- c++11在union上对98进行了很多改进,任何非引用类型都可以作为联合体数据,可以有静态函数,不能有静态变量。
- 如果非pod成员有非平凡的构造函数,那么联合体默认构造函数将会被删除,其他特殊成员函数(拷贝,移动,析构)遵循此规则。
unit T{
string s;
int n;
};
void main(){
T t;//构造失败,string有非平凡构造函数,T的默认构造函数被删除
}
因此,这种情况下我们要自己定义非受限联合体定义的构造函数,通常情况下,要使用placement new操作符。
unit T{
string s;
int n;
T(){ new(&s)string;}
~T(){s.~string();} //注意,析构的时候要保证t是一个string对象
};
void main(){
T t;//构造成功
}
用户自定义字面量
- 通过定义一个类的字面量操作符,就可以通过带后缀的字符串定义一个临时对象
class RGBA
{
RGBA operator ""_C (char * ch,int cnt){
//省略对字符串的解析
//code...
return *this;
}
int clr[4]
};
void blend(RGBA && c1,RGBA && c2);
void main(){
blend("r211 g0 b0 a123"_C,"r233 g32 b12 a34"_C);
}
C++字面量规则
* 如果字面量是整数,字面量操作符可接受unsigned long long 或 const char * (结尾自动添加’\0’)为参数。
* 如果字面量是整数,字面量操作符可接受long double 或 const char * 为参数。
* 如果字面量是字符串,字面量操作符可接受(const char *,size_t)为参数
* 如果字面量是字符,字面量操作符接受char为参数。
注意
* 操作符声明时operator”“与自定义后缀之间有空格
* 后缀要以下划线开始,否则编译器有警告,否则定义L这种会有混淆。
内联名字控件
命名空间下有子空间,使用时不是那么方便。
* namespace前加上inline关键字,可以让该命名空间定义的模板可以在父命名空间里进行特化。
namespace Jim{
namespace Basic{
struct Knife{Knife(){cout<<"Knife in Basic."<<endl;}};
class CorkScrew{};
}
namespace Toolkit{
template<typename T>class SwissArmyKnife{};
}
//...
namespace Other{
//...
}
//打开一些内部名字空间
using namespace Basic;
using namespace Toolkit;
}
template<>class SwissArmyKnife<Knife>{};//编译失败
}
using namespace Jim;
int main(){
SwissArmyKnife<Knife>sknife;
}
//编译选项:g++3-9-2.cpp
上面这个会编译失败
#include <iostream>
using namespace std;
namespace Jim{
inline namespace Basic{
struct Knife{Knife(){cout<<"Knife in Basic."<<endl;}};
class CorkScrew{};
}
inline namespace Toolkit{
template<typename T>class SwissArmyKnife{};
}
//...
namespace Other{
Knife b;//Knife in Basic
struct Knife{Knife(){cout<<"Knife in Other"<<endl;}};
Knife c;//Knife in Other
Basic::Knife k;//Knife in Basic
}
}
//这是LiLei在使用Jim的库
namespace Jim{
template<>class SwissArmyKnife<Knife>{};//编译通过
Jim;
int main(){
SwissArmyKnife<Knife>sknife;
}
//编译选项:g++ -std=c++11 3-9-3.cpp
这样的就编译通过。
使用inline命名空间会破坏良好的分隔,不过这跟inline的使用方式有关
#include <iostream>
using namespace std;
namespace Jim{
#if__cplusplus==201103L
inline
#endif
namespace cpp11{
struct Knife{Knife(){cout<<"Knife in c++11."<<endl;}};
//...
}
#if__cplusplus<201103L
inline
#endif
namespace oldcpp{
struct Knife{Knife(){cout<<"Knife in old c++."<<endl;}};
//...
}
}
using namespace Jim;
int main(){
Knife a;//Knife in c++11.(默认版本)
cpp11::Knife b;//Knife in c++11.(强制使用cpp11版本)
oldcpp::Knife c;//Knife in old c++.(强制使用oldcpp11版本)
}
//编译选项:g++ -std=c++11 3-9-4.cpp
模板别名
- 使用using比使用typedef更加灵活
typedef unsigned int uint;
using uint = unsigned int;
使用using更符合语言逻辑,使用typedef的地方都可以用using,而using还有更多的使用方式
template<typename T>
using MapString = std::map<T,char *>;
MapString<int> numberedString
sfinea规则
(substitution failure is not an error) 匹配失败不是错误
* 略