第三章-通用为本专用为末
3.1 继承构造函数
class Base {
public:
Base() { std::cout << "Base()" << std::endl; }
Base(int a) { std::cout << "Base(int a)" << std::endl; }
Base(int a, int b) { std::cout << "Base(int a, int b)" << std::endl; }
};
class ImpBase : public Base{
public:
using Base::Base;
ImpBase (int a) {
std::cout << "ImpBase (int a)" << std::endl;
}
};
int main (int argc, char *argv[]) {
ImpBase impBase;
std::cout << "--------" << std::endl;
ImpBase impBase2(10);
std::cout << "--------" << std::endl;
ImpBase impBase3(10, 20);
return 0;
}
out
Base()
--------
Base()
ImpBase (int a)
--------
Base(int a, int b)
参数的默认值是不会被继承的。事实上,默认值会导致基类产生多个构造函数的版本
3.2 委派构造函数
TODO,对象的初始化顺序,父类,子类,子对象,初始化列表,就地初始化,就是初始化成员类
TODO,委派构造函数
3.3 移动语义和完美转发
- 3.3.2 移动语义
从浅拷贝存在的问题谈起
class Base {
public:
Base() : data(new int(20)) { std::cout << "Base()" << std::endl; }
Base(const Base &b) :data(new int(*b.data)) { std::cout << "Base(const Base &b)" << std::endl; }
~Base() { std::cout << "~Base()" << std::endl; }
int *data;
};
Base get_base() {
return Base();
}
int main (int argc, char *argv[]) {
Base b = get_base();
return 0;
}
/*
g++ test.cpp -fno-elide-construcrors
out Base()
Base(const Base &b)
~Base()
Base(const Base &b)
~Base()
~Base()
不加-fno-elide-construcrors这个开关会直接优化掉
out Base()
~Base()
*/
一旦我们用到的是个临时变量,那么移动构造语义就可以得到执行
- 3.3.3 左值、右值与右值引用
一个最为典型的判别方法就是,在赋值表达式中,出现在等号左边的就是“左值”,而在等号右边的,则称为“右值”。不过C++中还有一个被广泛认同的说法,那就是可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值。那么这个加法赋值表达式中,&a是允许的操作,但&(b +c)这样的操作则不会通过编译。因此a是一个左值,(b + c)是一个右值。
更为细致地,在C++11中,右值是由两个概念构成的,一个是将亡值(xvalue,eXpiring Value),另一个则是纯右值(prvalue,Pure Rvalue)其中纯右值就是C++98标准中右值的概念,讲的是用于辨识临时变量和一些不跟对象关联的值。而将亡值则是C++11新增的跟右值引用相关的表达式,这样表达式通常是将要被移动的对象(移为他用),比如返回右值引用T&&的函数返回值、std::move的返回值,或者转换为T&&的类型转换函数的返回值。
在常量左值引用在C++98标准中开始就是个“万能”的引用类型,就是我们常说的常引用。
判断一个类型是否是引用类型,标准库<type_traits>头文件中定义的三个模板类。is_rvalue_reference、is_lvalue_reference、is_reference。eg.cout << is_rvalue_reference<string &&> << endl;
- 3.3.4 std::move强制转化为右值
在C++11中,标准库在
事实上,为了保证移动语义的传递,程序员在编写移动构造函数的时候,应该总是记得使用std::move转换拥有形如堆内存、文件句柄等资源的成员为右值,这样一来,如果成员支持移动构造的话,就可以实现其移动语义。而即使成员没有移动构造函数,那么接受常量左值的构造函数版本也会轻松地实现拷贝构造,因此也不会引起大的问题。
- 3.3.5 移动语义的一些其他问题
Moveable(const Moveable &&m)会使得的临时变量常量化,成为一个常量右值,那么临时变量的引用也就无法修改,从而导致无法实现移动语义。程序员在实现移动语义一定要注意排除不必要的const关键字。
只有移动语义构造的类型往往都是“资源型”的类型,比如说智能指针,文件流等,都可以视为“资源型”的类型。
在标准库的头文件cout << is_move_constructible<Type>::value
移动语义可以实现高性能的置换函数,swap,如下例子,加入T可移动
template<class T>
void swap(T &a, T &b) {
T tmp(move(a));
a = move(b);
b = move(tmp);
}
尽量编写不抛出异常的移动构造函数,添加一个noexcept关键字,
move_if_noexcept的用法
编译器中被称为RVO/NRVO的优化(RVO, Return Value Optimization,返回值优化,或者NRVO,Named Return Valueoptimization)
- 3.3.6 完美转发
所谓完美转发(perfect forwarding),是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。
std::forward
TODO ???????
3.4 显式转换操作符
explicit修饰构造函数
3.5 列表初始化
-
列表初始化
-
示例
int b[] {2, 4, 6}; vector<int> c{1, 3, 5}; map<int, float> d = {{1, 1.0f}, {3, 9.9f}}; -
这样一来,自动变量和全局变量的初始化在C++11中被丰富了。程序员可以使用以下几种形式完成初始化的工作: 而后两种形式也可以用于获取堆内存new操作符中,
- 等号“=”加上赋值表达式(assignment-expression),比如int a = 3 + 4。
- 等号“=”加上花括号式的初始化列表,比如int a = {3 + 4}。
- 圆括号式的表达式列表(expression-list),比如int a (3 + 4)。
- 花括号式的初始化列表,比如int a {3 + 4}
-
只要#include了
头文件,并且声明一个以initializer_list 模板类为参数的构造函数,同样可以使得自定义的类使用列表初始化,例如: #include <iostream> #include <vector> enum Gender {boy, girl}; class Test{ public: Test(std::initializer_list<std::pair<int, Gender>> t) { for (auto &i : t) { t_.push_back(i); } } std::vector<std::pair<int, Gender>> t_; }; int main() { Test t({{1, boy}, {2, girl}}); return 0; } -
防止类型收窄
列表初始化会检查是否发生类型收窄的,比如const int a = 10245;char b = {a};,会编译不过(但是不加const可以编译过去)
在C++11中,列表初始化是唯一一种可以防止类型收窄的初始化方式。
3.6 POD类型
POD是英文中Plain Old Data的缩写。POD可以使用memcpy进行复制,使用memset进行初始化。
C++11将POD划分为两个基本概念的合集,即:平凡的(trivial)和标准布局的(standardlayout)
-
平凡的(trivial) 使用默认的构造函数,析构函数,复制构造函数,拷贝构造函数,移动构造函数,不能包含虚函数以及虚基类
-
标准布局的(standardlayout)