Type Inference
template:模板推导的时候,参数是指针或者引用,但不是万能引用。
template
void f(T& param);
int x = 27;
const int cx = x;
const int& rc = x;
f(rc) -> 引用会被去掉,会变成const int。所以指针和引用下,引用或者指针会被去掉。
param是万能引用,
f(T&& param);
会根据是左值还是右值进行推导。
f(T param);
按值的话,都会变成值。
另外考虑数组或者函数类型的实参退化。
auto:和上面一样,除了auto可以使用大括号初始化一个std::initializer_list。
decltype:就是获取改名字的declread type。
template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i) -> decltype(c[i]) {
autheenticateUser();
return c[i];
}
Widget w;
const Widget& cw = w;
auto myWidget1 = cw; // Widget
decltype(auto) myWidgte2 = cw; // const Widget&
see type:使用typeid或者std::type_info看类型。
auto
auto is better。auto使用了类型推导。并且lambda里面可以使用auto。
auto derefLess = { return *p1 < *p2; };
std::function<bool (const std::unique_ptr
&, const std::unique_ptr &)> func; std::function比lambda更大更慢。
auto不符合要求的时候,带有显式型别的初始化方法。比如有时候类别返回的是reference,代理类,其实你需要的是具体的内容,这时候需要转化为具体的。auto sum = static_cast
(m1 + m2 + m3 + m4); 这时候不使用auto更加清晰,比如:Matrix sum = m1 + m2 + m3 + m4; 这里就会做隐式转换。
modern c++
() and {}。优先选择()而不是{},{}不适合用来初始化。也是非常让人疑惑的,只有C++这么做。但是{}的好处是会进行阻止隐式类型转化;以及解析语法错误(很少用到){}有的问题是如果有initilaizer_list的时候为形参的时候。
优先使用nullptr,而不是0或NULL。消除NULL的多义型,导致重载的时候调用错函数。nullptr没有整型类型,也没有指针类型。
优先使用别名using,而不是typedef。typedef不支持模板化。
优先使用enum class,而不是enum。使用tuple的时候,size_t来获取元素,更让代码清晰。
优先使用删除函数,delete,而不是private未定义的函数。
为改写的函数增加override。写在多态函数的结尾,同类似的还有final。
优先使用const_iterator,而不是iterator。
只要函数不会发射异常,就为其加上noexcept声明。
只要有可能使用constexpr,就尽量使用。
保证const成员函数的线程安全性:如果只是单个进行同步的变量,使用std::atomic即可,否则使用互斥量。
理解特种成员函数的生成机制。默认构造函数、析构函数、复制构造函数、复制赋值运算符、移动构造函数和移动赋值运算符。成员模板函数不会抑制特种成员函数的生成。
智能指针
unique_ptr:管理专属所有权的资源。可以自定义析构器。使用unique_ptr的reset来实现factory mode。
shared_ptr:管理共享所有权的资源。裸指针的两倍,指向资源的指针和指向引用计数的指针。
shared_ptr会悬空的时候,使用weak_ptr。以及shared_ptr的指针环路。
make_unique, make_shared,消除了异常。
右值引用 & 移动语义 & 完美转发
move & forward: move是无条件强制类型转换,转成右值;forward需要加模板类型,以及需要输入的是右值才会进行强制类型转换为右值。运行时,两者都不会做任何操作。forward可以将传进来的右值,转为右值。实参使用右值完成初始化的时候,才会执行向右值型别的强制转换。
forward的一个case,
void logAndProcess(T&& param) {
process(std::forward
(param)); }
T&&类型,如果T是由类型推导或者是auto&&声明类型的时候,该形参或者对象就是个万能引用。类型不具备type&&或类型推导没有发生,就是右值引用。右值来初始化万能引用,那么就是右值引用;如果使用左值来初始化万能引用,就会得到左值引用。
对于右值引用最后一次实施std::move,对于万能引用最后一次实施forward。局部对象的返回值优化,不要使用:move或者forward。
// 避免万能引用型别进行重载
// 把万能引用做重载,导致意外调用。完美转发的构造函数问题,尤为严重。
不使用万能引用和重载的组合,课使用const &T,传值,标签分派。使用enable_if,http://www.cplusplus.com/reference/type_traits/enable_if/
template<typename T,
typename = typename std::enable_if<
! std::is_base_of <Person, typename std::decay
>::type>::valu>>::type > explicit Person(T&& n);
为什么使用forward,其中一个原因就是引用折叠。如果输入的是左值,虽然有&&,但是还是会被推导成右值。所以必须使用forward,强制转化为右值。引用折叠,会在模板实例化,auto型别生成,创建和使用typedef,using,decltype的时候使用。
有些情况下,array,string,移动并没有比拷贝快。可以假定这些类型是不存在,高成本,未使用。对于已知的情况,无需做上面的假设。
完美转发指的是可以转发对象,显著特征,左值或者是右值,以及是否有const,volation等等修饰。万能引用才会对左右值进行编码。完美转发,值的是源于模板型别推导失败,或者是推导结果错误的型别。完美转发失败的例子有:大括号初始化,以0或者NULL表达的空指针,仅有声明的整型static const成员变量,模板或者重载的函数名字,以及位域。
Lambda
lambda:可以有闭包。auto c1 = [ x ] (int y) { return x * y > 55; } 为了避免lambda的悬空,可以使用拷贝到本地。this指针会在class里面在=的时候被传送。按引用的默认捕获会导致空悬指针的问题;按值默认捕获会受到空悬指针影响(尤其是this)。
void Widget::addFilter() const {
auto divisorCopy = divisor;
filters.emplace_back(
[divisor = divisor] (int value) {
return value % divisor == 0;
}
);
}
使用初始化捕获将对象移入闭包。比如:
auto func = [pw = std::make_unique
( )] { return pw->isValidated() && pw->isArchived();
}
lambda里面可以使用auto。使用auto&&型别的形参进行decltype,以std::forward。
auto f = [] (auto&& param) {
return func(normalized(std::forward<decltype(param)>(param)));
}
在C++14里面,可以使用变长参数
auto f = [] (auto&& … param) {
return func(normalized(std::forward<decltype(param)>(param) …));
}
lambda比bind可读性更好,表达力更强,可能运行效率更高。
并发API
优先使用基于任务,而不是线程的设计。
基于任务: auto fut = std::async(doAsyncWork);
基于线程:std::thread t(doAsyncWork);
基于任务可以获取状态,不会被terminate。同时可以不需要进行人为的线程管理。自己做调度,很容易发生负载不均衡。
如果异步是必须的,就是用launch::async,因为async不一定可以实现目标。async默认既允许同步,也允许异步。thread_pool的变量不确定性,使任务永远不会被执行。
std::thread: join可能导致调试的性能异常;析构调用detach可能导致难以调试的未定义行为。成员列表最后声明std::thread型别对象。型别对象所在的路径皆不可连接。
期待的析构函数在常规情况下,仅会析构期待值的成员变量。由async启动的任务是会保持阻塞的,直至任务结束。
一次性事件,一般使用pingbang原则进行。
比如:
std::condition_variable cv;
std::mutex m;
bool flag(false);
{
std::lock_guard<std::mutex > g(m);
flag = true;
}
cv.notify_one(); // notify_all
{
std::unique_ptr<std::mutex > lk(m);
cv.wair(lk, [] { return flag; } );
}
atomic,特别适合单一资源的使用。std::atomic
ai(0); ai = 10;
std::cout « ai « std::endl;
++ai;
–ai;
如果只有一个线程修改ai,其它线程看到ai的值只能是0,10,11。但是volatile可能是任何值。
register = x.load();
std::atomic
y(register); y.store(register);
微调(fine-tune)
针对可复制的形参,在移动成本低并且一定会被复制的前提下,考虑按值传递。
三种方法:
法一:
void addName(const std::string& newName) {
names.push_back(newName);
}
void addName(const std::string&& newName) {
names.push_back(std::move(newName));
}
法二:
template
void addName(T&& newName) {
names.push_back(std::forward
(newName)); }
法三:
void addName(std::string newName) {
names.push_back(std::move(newName));
}
考虑置入而不是插入:插入多了构造和析构的环节。使用emplace_back,使用了完美转发,参数出现在vector的构造函数中了。
