operator()
的类(仿函数)auto
的类型推导是基于模板的类型推导。f(T* param)
,f(T& param)
或f(const T& param)
等,根据传入的实参来推导类型时,会先省略实参的引用属性,即此时实参 const int i
与 const int& i
是等价的。f(T&& param)
时, 如果实参为左值,那么 T 会被推导为左值引用(比如 int&
), 这是 T 被推导为左值引用的唯一情况; 如果实参为右值,则与上条相同。f(T param)
时,即按值传递,类型推导时会忽略实参的引用属性、const 属性和 volatile 属性。只有按值传递时类型推导才忽略实参的 const 属性。形参 | 实参 | 推导结果 |
---|---|---|
f(T*), f(T&), f(const T&) | int i; | T ⇒ int |
f(T*), f(T&), f(const T&) | int& ri=i; | T ⇒ int |
f(T*), f(T&) | const int& ci=i; | T ⇒ const int |
f(const T&) | const int& ci=i; | T ⇒ int |
f(T&& param) | lvalue 左值i,ri | T ⇒ int&; param ⇒ int& |
f(T&& param) | rvalue 右值 f(27); f(std::move(i)) | T ⇒ int; param ⇒ int&& |
f(T param) | int i; volatile int i; const int ci=i; const int& ri=i; | T ⇒ int; param ⇒ int |
f(T param) | const char name[]=“oakfire” | T ⇒ const char* |
f(T& param) | const char name[]=“oakfire” | T ⇒ const char[8] |
f(T param) | void someFunc(int, double) | T ⇒ void (*)(int,double) |
f(T& param) | void someFunc(int, double) | T ⇒ void (&)(int,double) |
auto
的类型推导与模板类型推导基本一致,但对于花括号初始化值,比如 auto i = {1};
, i
会被推导为 std::initializer_list
类型,而模板不会推导为此类型,它会继续推导,类型不能确定即报告错误。auto
在作为函数返回值以及 lambda 参数时,则和模板类型推导完全一致。decltype
基本是给出变量或表达式本身的类型,不会做修改;T
类型的左值表达式(一般表达式都是右值), decltype
给出的是引用类型 T&
;int x=0
, decltype(x)
的结果是 int
, decltype( (x) )
的结果则是 int&
, 因为 C++ 规定带括号的表达式为左值。decltype(auto)
,它和 auto
一样,从其初始化器中推导出一个类型,但它使用 decltype
规则来执行类型推导。Boost.TypeIndex
库来运行时打印信息通常可以看到推导出的类型。auto
声明的变量必须被初始化,可规避初始化遗忘问题;auto
可规避因为显式类型声明不匹配导致的移植性问题或性能问题;auto
可简化重构过程;auto
可节省打字输入时间!auto
类型要受制于条目2 与条目6所提到的陷阱。std::vector<bool>
的 []
返回类型不是 bool&
而是 std::vector<bool>::reference
.(因为早期C++专门针对 std::vector<bool>
做过优化),此时 auto
并不能推导出期望的 bool
类型。std::vector<bool>
的”不可见”代理类,会导致 auto
不能推导出期望类型。auto
推导出期望类型: std::vector<bool> f(const W& w);auto a = static_cast<bool>(f(w)[5]);
int i = {0}
等号加大括号的初始化方式等同于只用大括号 int i{0}
Widget w();
既可解析为初始化对象 w, 也可解析为声明一个返回 Widget 对象的函数 w,但 Widget w{}
就没有这种歧义。std::initializer_list
参数,即使其他构造函数提供了看似更好的匹配。std::vector<numeric-type>
:std::vector<int> v1(10, 20)
表示初始化10个元素的 vector,其中每个值都为 20;std::vector<int> v1{10, 20}
表示初始化两个元素的 vector, 值分别为 10 与 20// FP is a synonym for a pointer to a function taking an int and // a const std::string& and returning nothing typedef void (*FP)(int, const std::string&); // typedef same meaning as above using FP = void (*)(int, const std::string&); // alias declaration
typedef
用于模板时需要 typename
与后缀 ::type
想配合。std::remove_const<T>::type
, c++14 提供了这些类型的别名版本, 例如:std::remove_const_t<T>
enum class
, 则只在域内可见,也只可通过显式类型转换来转换类型enum class
则是有基础类型 int
enum class Color;
, 非现定域则需要指定基础类型才可前置声明 enum Color: std::uint8_t;
Widget(const Widget& ) = delete; Widget& operator=(const Widget&) = delete;
class Widget { public: … void doWork() &; // this version of doWork applies only when *this is an lvalue void doWork() &&; // this version of doWork applies only when *this is an rvalue }; … Widget makeWidget(); // factory function (returns rvalue) Widget w; // normal object (an lvalue) w.doWork(); // calls Widget::doWork for lvalues makeWidget().doWork(); // calls Widget::doWork for rval
override
修饰词可规避因细微声明差异导致未覆盖。const_iterator
支持更好,并提供了 cbegin, cend
等返回 const 迭代器的函数。begin,end,rbegin
等,而不是其对应的成员函数。std::cbegin, std::cend
等非成员函数版本。noexpect
属于接口声明的一部分, 调用者依赖它。noexpect
利于编译期优化。noexpect
应用于移动操作,swap,内存释放函数与析构函数最有价值。noexpect
。constexpr
对象在编译期初始化值,初始化后为 const, 可认为是常量;const
对象则可能是在运行期才初始化值,保证值不变。constexpr
函数在编译期就能产生结果值,如果实参在编译期就能得到。constexpr
属于接口声明的一部分,“尽可能”包括表示尽量不要在接口声明了constexpr又去掉。std::atomic
性能可能优于互斥量,但仅适用于单个变量或单个内存地址的操作。Widget(Widget&& rhs);
Widget& operator=(Widget&& rhs);
template<typename T> Widget(const T& rhs);
)不会限制特制成员函数的生成。std::auto_ptr
c++98 遗留,已废弃,可用 std::unique_str
代替std::unique_ptr
std::shared_ptr
std::weaked_ptr
std::unique_ptr
轻量,快速,只可移动(move-only), 不可复制;std::unique_ptr
释放资源默认使用 delete
, 也可自定义删除器(deleter);自定义删除器的状态与函数指针可增加std::unique_ptr
对象的大小;std::unique_prt
可轻易得转为 std::shared_prt
std::shared_ptr
使用引用计数(reference count)来管理资源,当引用计数为0(最后一个指向该资源的shared_ptr析构)时释放资源,实现资源的生命周期管理;std::shared_ptr
对象析构、构造时修改引用计数,但是移动构造与移动赋值直接省略了减一加一过程,比拷贝构造拷贝赋值要更有效率;std::shared_ptr
可自定义删除器,但删除器不作为其类型的一部分,而 std::unique_ptr
把删除器作为其类型的一部分:// 自定义删除器 auto loggingDel = [](Widget *pw) { makeLogEntry(pw); delete pw; }; //unique_ptr指针声明包含了删除器类型 std::unique_ptr<Widget, decltype(loggingDel)> upw(new Widget, loggingDel); //shared_ptr指针声明不包含删除器类型 std::shared_ptr<Widget> spw(new Widget, loggingDel);
std::make_shared
, 此时能确保是首次创建资源对象;std::unique_ptr
(或 std::auto_ptr
)对象构造std::shared_ptr
,此时能确保独占对象不存在控制块,并且所有权已从独占指针转移;std::shared_ptr
也会创建控制块,但此时不能确保原始指针指向的对象不存在控制块,使用同一个原生指针构造两次std::shared_ptr
就会产生未定义行为std::shared_ptr
;std::enable_shared_from_this
基类模板(The Curiously Recurring Template Pattern(CRTP))可安全得使用 this
指针构造 std::shared_ptr
std::unique_ptr
不同, std::shared_ptr
设计之初就是针对单个对象的, 没有办法std::shared_ptr<T[]>, 不适用于直接指向数组, 应使用标准容器。std::weak_ptr
从 std::shared_ptr
创建,但不参与共享资源的所有权引用计数auto spw = std::make_shared<Widget>(); // 引用计数+1 std::weak_ptr<Widget> wpw(spw); // 引用计数不变 spw = nullptr; // 引用计数-1,资源被释放, wpw 悬空 wpw.reset(); // 引用计数不变, 置空
wpw.expired()
可判断弱指针指向的资源是否已释放,即指针本身是否悬空(dangle);std::weak_ptr
本身不能解引用,要使用资源时,需使用wpw.lock()
或直接构造std::shared_ptr
来使用:std::shared_ptr<Widget> spw1 = wpw.lock(); // 如果wpw过期,则 spw1 为 null std::shared_ptr<Widget> spw3(wpw); // 如果wpw过期,抛异常std::bad_weak_ptr
std::weak_ptr
的适用场景:make
方式 比 new
方式提高了异常安全性:std::shared_ptr<Widget> pw(new Widget)
包含了两步操作:1.new 对象;2.构建指针processWidget(std::shared_ptr<Widget>(new Widget), computePriority());
由于没有规定函数实参构建顺序,编译器可能按如下顺序构建实参:
std::make_shared
和 std::allocate_shared
方式生成的代码更快更小:对象与控制快内存一起分配、一起销毁。make
方式的情况: std::shared_ptr
的其它情况std::weak_ptr
比对应的 std::shared_ptr
更长久(弱指针存在导致控制块不能销毁,导致make产生的对象内存块也没有销毁)。std::unique_ptr
pImpl 指针, 在头文件声明特制成员函数,实现则要在实现文件里定义,即使编译器默认实现是可接受的:// 以下都在实现文件 widge.cpp 中: Widget::~Widget() = default; // 防止报错 // 自定义析构导致拷贝与移动构造不能默认生成,手动生成需定义在实现文件 Widget::Widget(Widget&& rhs) = default; // 注意默认实现是浅拷贝,需深拷贝自己实现 Widget& Widget::operator=(Widget&& rhs) = default;
std::shared_ptr
指针没有上述限制,但不适用于 Pimpl 独享占有权语义。std::move
无条件转换为一个右值,其本身并不移动任何东西:// std::move 的简单实现: template<typename T> // C++14; in namespace std decltype(auto) move(T&& param) { using ReturnType = remove_reference_t<T>&&; return static_cast<ReturnType>(param); }
const
, 因为 const 实参会默认进入拷贝构造而不是移动构造std::forward
仅当形参来源于右值实参时,才把它转为右值(作为下个函数的实参);std::move
与 std::forward
在运行时 do nothing。T&&
并且 T
需类型推导; auto&&
。type&&
,或者不存在类型推导,那么 type&&
为右值引用:void f(Widget&& param); // rvalue reference Widget&& var1 = Widget(); // rvalue reference auto&& var2 = var1; // not rvalue reference template<typename T> void f(std::vector<T>&& param); // rvalue reference template<typename T> void f(T&& param); // not rvalue reference
std::move
, 通值引用的形参使用 std::forward
。std::move
, std::forward
(因为使用之后该对象就会失效)。std::move
或 std::forward
。std::move
隐式应用于返回的局部对象,所以,不需要对要返回的局部对象(或值形参)手动使用 std::move
或 std::forward
。int
重载,通值引用的函数会更匹配 short
参数: template<typename T> void logAndAdd(T&& name){} // origon_func void logAndAdd(int idx){} // overload_func short index; logAndAdd(index); //Error! it match origon_func, not overload_func
通值引用的重载,会让通值引用比预期中更频繁得被调用。
英 | 汉 | 备注 |
---|---|---|
argument | 实参 | |
parameter | 形参/参数 | |
basic guarantee | 基本异常保障 | basic exception safty guarantee |
strong guarantee | 强异常保障 | strong exception safty guarantee |
built-in pointer | 内置指针 | 相对于智能指针的原指针概念 |
raw pointer | 原生指针 | |
universal reference | 通值引用 | 相对“左值引用”、“右值引用”来说 |
scoped enums | 限定域枚举 | |
unscoped enums | 非限定域枚举 | |
forward-declared | 前置声明 | |
override | 覆盖 | |
overload | 重载 | |
maximally generic code | 最泛化代码 | |
special member function | 特制成员函数 | |