Table of Contents:

容器:emplace系列函数,减少拷贝的开销
auto/decltype
类中的:
委托构造
构造函数继承
成员变量类内初始化
类型别名
final
delete
explicit
override :Specifies that a virtual function overrides another virtual function.

属性(attribute)
静态断言(static_assert)
constexpr 关键字

* 字符串
字符串字面量后缀  后缀s
原始字符串  R
string_view
正则表达式库

emplace

用法

struct Foo {
    Foo(int n, double x);
};

std::vector v;
v.emplace(someIterator, 42, 3.1416); // 没有临时变量产生
v.insert(someIterator, Foo(42, 3.1416)); // 需要产生一个临时变量
v.insert(someIterator, {42, 3.1416}); // 需要产生一个临时变量

做到这一点主要 使用了 C++11 的两个新特性 变参模板变参模板 和 完美转发完美转发。”变参模板”使得 emplace 可以接受任意参数,这样就可以适用于任意对象的构建。
”完美转发”使得接收下来的参数 能够原样的传递给对象的构造函数

map 的特殊情况

无法区分哪个参数用来构造 key 哪些用来构造 value, 比如key可能需要两个参数构造,value只需要一个参数构造

map<string, complex> scp;
scp.emplace(“hello”, 1, 2);

解决:用tuple组合起来已进行区分

map<string, complex<double>> scp;
scp.emplace(piecewise_construct,
    forward_as_tuple("hello"),
    forward_as_tuple(1, 2));

所以对于 map 来说你虽然避免了临时变量的构造,但是你却需要构建两个 tuple 。 这种 traedoff 是否值得需要代码编写者自己考虑,从方便性和代码优雅性上来说:

scp.insert({"world", {1, 2}});

std::tie

std::tie:创建左值引用的 tuple,或将 tuple 解包为独立对象

// std::tie 用于引入字典序比较到结构体
struct S {
    int n;
    std::string s;
    float d;
    bool operator<(const S& rhs) const {
        // 比较 n 与 rhs.n,
        // 而后为 s 与 rhs.s,
        // 而后为 d 与 rhs.d
        return std::tie(n, s, d) < std::tie(rhs.n, rhs.s, rhs.d);
    }
};
// 通过tie将tuple中的元素解构至多个变量中  
auto student = std::make_tuple(3.8, 'A', "Lisa Simpson");
double gpa1;
char grade1;
std::string name1;
std::tie(gpa1, grade1, name1) = student;

std::tie 可用于解包 std::pair ,因为 std::tuple 拥有从 pair 的转换赋值

bool result;
std::tie(std::ignore, result) = set.insert(value);

C++11 shared_ptr(智能指针)详解

要确保用 new 动态分配的内存空间在程序的各条执行路径都能被释放是一件麻烦的事情。C++ 11 模板库的 <memory> 头文件中定义的智能指针,即 shared_ptr 模板,就是用来部分解决这个问题的。
托管 p 的 shared_ptr 对象在消亡时会自动执行delete p。而且,该 shared_ptr 对象能像指针 p —样使用,即假设托管 p 的 shared_ptr 对象叫作 ptr,那么 *ptr 就是 p 指向的对象。
只有指向动态分配的对象的指针才能交给 shared_ptr 对象托管。将指向普通局部变量、全局变量的指针交给 shared_ptr 托管,编译时不会有问题,但程序运行时会出错,因为不能析构一个并没有指向动态分配的内存空间的指针。

注意
1. 不能用下面的方式使得两个 shared_ptr 对象托管同一个指针:

A* p = new A(10);
shared_ptr <A> sp1(p), sp2(p);

sp1 和 sp2 并不会共享同一个对 p 的托管计数,而是各自将对 p 的托管计数都记为 1(sp2 无法知道 p 已经被 sp1 托管过)。这样,当 sp1 消亡时要析构 p,sp2 消亡时要再次析构 p,这会导致程序崩溃。

2. 不要轻易的使用get()获得raw pointer,然后操作它

C++11 Lambda表达式

实现方式可能为函数对象,一个lambda就是一个可调用对象。
主要作用:
1.将函数变成first class。
2.做回调函数。

[capture](parameters)->return-type{body}
[外部变量访问方式说明符(=|&)] (参数表) -> 返回值类型
{
   语句块
}
[](int x, int y) -> int { int z = x + y; return z + x; }
[]        // 沒有定义任何变量。使用未定义变量会引发错误。
[x, &y]   // x以传值方式传入(默认),y以引用方式传入。
[&]       // 任何被使用到的外部变量都隐式地以引用方式加以引用。
[=]       // 任何被使用到的外部变量都隐式地以传值方式加以引用。
[&, x]    // x显式地以传值方式加以引用。其余变量以引用方式加以引用。
[=, &z]   // z显式地以引用方式加以引用。其余变量以传值方式加以引用。
[this]    // 捕获对象的this指针
int main()
{
    int x = 100,y=200,z=300;
    auto ff = [=,&y,&z](int n) { //x是值传递,y和z是引用传递
        cout <<x << endl;
        y++; z++;
        return n*n;
    };
    cout << ff(15) << endl;
    cout << y << "," << z << endl;
}

lambda捕获this指针或成员变量

class Example
{
public:
    Example() : m_var(10) {}
    void func()
    {
        [=]()
        { std::cout << m_var << std::endl; }(); //立即调用
    }

private:
    int m_var;
};
int main()
{
    Example e;
    e.func();
}

捕获this指针也可以使用 [this], [=]或者 [&]。在上述任何情况下,类内数据成员(包括 private)的访问方式与常规方法一样。

C++11 auto和decltype关键字

变量的类型名特别长,使用 auto 就会很方便。

有时我们希望从变量或表达式推断出要定义的变量类型,decltype 关键字可以用于求表达式的类型

int i;
double t;
struct A { double x; };
const A* a = new A();
decltype(a) x1; //x1 是 A*
decltype(i) x2; //x2 是 int
decltype(a->x) x3; // x3 是 double

C++11右值引用

引入右值引用的主要目的是提高程序运行的效率。有些对象在复制时需要进行深拷贝,深拷贝往往非常耗时。合理使用右值引用可以避免没有必要的深复制操作。

class A{};
A & rl = A(); //错误,无名临时变量 A() 是右值,因此不能初始化左值引用 r1
A && r2 = A(); //正确,因 r2 是右值引用

lvalue 是“locator value”的缩写,可意为存储在内存中、有明确存储地址(可寻址)的数据
而 rvalue 译为 "read value",指的是那些可以提供数据值的数据(不一定可以寻址,例如存储于寄存器中的数据)。

通常情况下,判断某个表达式是左值还是右值,最常用的有以下 2 种方法:
 可位于赋值号(=)左侧的表达式就是左值;反之,只能位于赋值号右侧的表达式就是右值
 有名称的、可以获取到存储地址的表达式即为左值;反之则是右值。

和声明左值引用一样,右值引用也必须立即进行初始化操作,且只能使用右值进行初始化
虽然 C++98/03 标准不支持为右值建立非常量左值引用,但允许使用常量左值引用操作右值
和常量左值引用不同的是,右值引用还可以对右值进行修改

C++11移动语义是什么

所谓移动语义,指的就是以移动而非深拷贝的方式初始化含有指针成员(动态分配内存)的类对象。简单的理解,移动语义指的就是将其他对象(通常是临时对象)拥有的内存资源“移为已用”。

我们知道,非 const 右值引用只能操作右值,程序执行结果中产生的临时对象(例如函数返回值、lambda 表达式等)既无名称也无法获取其存储地址,所以属于右值。当类中同时包含拷贝构造函数和移动构造函数时,如果使用临时对象初始化当前类的对象,编译器会优先调用移动构造函数来完成此操作。只有当类中没有合适的移动构造函数时,编译器才会退而求其次,调用拷贝构造函数。

在实际开发中,通常在类中自定义移动构造函数的同时,会再为其自定义一个适当的拷贝构造函数,由此当用户利用右值初始化类对象时,会调用移动构造函数;使用左值(非右值)初始化类对象时,会调用拷贝构造函数。

只有移动没有拷贝的例子
1. std::unique_ptr<>
2. std::mutex

C++中用户类默认的移动构造/移动赋值和启用条件

移动构造和移动赋值是c++实现零拷贝的利器。对于一个简单的用户类,在满足一定条件时,编译器会提供默认的移动构造和移动赋值实现:默认实现会对类成员变量递归地进行移动构造和移动赋值。如果用户类内包含STL容器这种本身有移动实现的成员变量,又不包含非RAII的资源管理,那么默认移动逻辑是完全够用的。

但是,启用默认移动构造函数必须满足以下全部条件
1.  没有声明拷贝赋值函数。
2.  没有声明拷贝构造函数。
3.  没有声明移动赋值函数。
4.  移动构造函数没有隐式声明为delete(参考这里,简单类一般不需要考虑)。
5.  没有声明析构函数。

同时,启用默认移动赋值函数必须满足以下全部条件
1.  没有声明拷贝赋值函数。
2.  没有声明拷贝构造函数。
3.  没有声明移动构造函数。
4.  移动赋值函数没有隐式声明为delete(参考这里,简单类一般不需要考虑)。
5.  没有声明析构函数。

关于第5点其实也很容易理解。因为析构函数不可省略的场景往往涉及资源的手动分配和释放,这时候移动构造和移动赋值一般需要进行诸如交接所有权的额外逻辑。所以在用户类已声明析构函数时,编译器不提供默认的移动逻辑以降低非预期行为的可能性。

C++11 move()函数:将左值强制转换为右值

move 本意为 "移动",但该函数并不能移动任何数据,它的功能很简单,就是将某个左值强制转化为右值

C++11完美转发及实现方法详解

完美转发,它指的是函数模板可以将自己的参数“完美”地转发给内部调用的其它函数。所谓完美,即不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变。
C++11 标准中规定,通常情况下右值引用形式的参数只能接收右值,不能接收左值。但对于函数模板中使用右值引用语法定义的参数来说,它不再遵守这一规定,既可以接收右值,也可以接收左值(此时的右值引用又被称为万能引用)。
在实现完美转发时,只要函数模板的参数类型为 T&&,则 C++ 可以自行准确地判定出实际传入的实参是左值还是右值。

通过将函数模板的形参类型设置为 T&&,我们可以很好地解决接收左、右值的问题。但除此之外,还需要解决一个问题,即无论传入的形参是左值还是右值,对于函数模板内部来说形参既有名称又能寻址,因此它都是左值。那么如何才能将函数模板接收到的形参连同其左、右值属性,一起传递给被调用的函数呢?

总的来说,在定义模板函数时,我们采用右值引用的语法格式定义参数类型,由此该函数既可以接收外界传入的左值,也可以接收右值;
其次,还需要使用 C++11 标准库提供的 forword() 模板函数修饰被调用函数中需要维持左、右值属性的参数。由此即可轻松实现函数模板中参数的完美转发。

template <typename T>
void function(T&& t) {
    otherdef(forward<T>(t));
}

C++11 constexpr:验证是否为常量表达式

所谓常量表达式,指的就是由多个(≥1)常量组成的表达式。常量表达式一旦确定,其值将无法修改。
我们知道,C++ 程序的执行过程大致要经历编译、链接、运行这 3 个阶段。值得一提的是,常量表达式和非常量表达式的计算时机不同,非常量表达式只能在程序运行阶段计算出结果;而常量表达式的计算往往发生在程序的编译阶段,这可以极大提高程序的执行效率,因为表达式只需要在编译阶段计算一次,节省了每次程序运行时都需要计算一次的时间。
对于用 C++ 编写的程序,性能往往是永恒的追求。那么在实际开发中,如何才能判定一个表达式是否为常量表达式,进而获得在编译阶段即可执行的“特权”呢?除了人为判定外,C++11 标准还提供有 constexpr 关键字。
constexpr 关键字的功能是使指定的常量表达式获得在程序编译阶段计算出结果的能力,而不必等到程序运行阶段。C++ 11 标准中,constexpr 可用于修饰普通变量、函数(包括模板函数)以及类的构造函数。

 注意,获得在编译阶段计算出结果的能力,并不代表 constexpr 修饰的表达式一定会在程序编译阶段被执行,具体的计算时机还是编译器说了算。

C++11 constexpr和const的区别详解

在C++ 98之前const是有两种语义的:1.只读 2.常量
C++ 11标准中,为了解决 const 关键字的双重语义问题,保留了 const 表示“只读”的语义,而将“常量”的语义划分给了新添加的 constexpr 关键字。因此 C++11 标准中,建议将 const 和 constexpr 的功能区分开,即凡是表达“只读”语义的场景都使用 const,表达“常量”语义的场景都使用 constexpr。
只读”和“常量”之间并没有必然的联系

int main()
{
    int a = 10;
    const int & con_b = a;
    cout << con_b << endl;
    a = 20;
    cout << con_b << endl;
}

程序中用 const 修饰了 con_b 变量,表示该变量“只读”,即无法通过变量自身去修改自己的值。但这并不意味着 con_b 的值不能借助其它变量间接改变,通过改变 a 的值就可以使 con_b 的值发生变化。
总的来说在 C++ 11 标准中,const 用于为修饰的变量添加“只读”属性;而 constexpr 关键字则用于指明其后是一个常量(或者常量表达式),编译器在编译程序时可以顺带将其结果计算出来,而无需等到程序运行阶段,这样的优化极大地提高了程序的执行效率。