To Top
首页 > 基础知识 > 正文

c++11新特性

标签:c++11


目录

参考: C++11新特性

英文维基百科: c++11 中文维基百科:c++11

0. 简介

C++11,之前被称作C++0x,即ISO/IEC 14882:2011,是目前的C++编程语言的正式标准。它取代第二版标准ISO/IEC 14882:2003(第一版ISO/IEC 14882:1998发布于1998年,第二版于2003年发布,分别通称C++98以及C++03,两者差异很小)。新的标准包含了几个核心语言增加的新特性,而且扩展C++标准程序库,并入了大部分的C++ Technical Report 1程序库(数学的特殊函数除外)。最新的消息被公布在 ISO C++ 委员会网站(英文)。 ISO/IEC JTC1/SC22/WG21 C++ 标准委员会计划在2010年8月之前完成对最终委员会草案的投票,以及于2011年3月召开的标准会议完成国际标准的最终草案。然而,WG21预期ISO将要花费六个月到一年的时间才能正式发布新的C++标准。为了能够如期完成,委员会决定致力于直至2006年为止的提案,忽略新的提案。最终,于2011年8月12日公布,并于2011年9月出版。2012年2月28日的国际标准草案(N3376)是最接近于现行标准的草案,差异仅有编辑上的修正。 像C++这样的编程语言,通过一种演化的过程来发展其定义。这个过程不可避免地将引发与现有代码的兼容问题。不过根据Bjarne Stroustrup(C++的创始人,标准委员会的一员)表示,新的标准将几乎100%兼容现有标准。

1. 核心语言的运行期表现强化

提升某些性能表现,例如内存或是速度上的表现。

1.1 右值引用和move语义

在C++03及之前的标准中,临时对象(称为右值”R-values”,因为通常位于赋值运算符的右边)的值是不能改变的,和C语言一样, 且无法和 const T& 类型做出区分。尽管在某些情况下临时对象的确会被改变,甚至有时还被视为是一个有用的漏洞。C++11新增加了一个非常量引用(non-const reference)类型,称作右值引用(R-value reference),标记为T &&。右值引用所引用的临时对象可以在该临时对象被初始化之后做修改,这是为了允许 move 语义

C++03 性能上被长期诟病的问题之一,就是其耗时不必要深拷贝。深拷贝会隐式地发生在以传值的方式传递对象的时候。例如,std::vector内部封装了一个C风格的数组和其元素个数,如果创建或是从函数返回一个std::vector的临时对象,要将其保存起来只能通过生成新的std::vector对象并且把该临时对象所有的数据复制进去。该临时对象和其拥有的內存会被销毁。(为简单起见,这里忽略了编译器的返回值优化)

参考https://zh.cppreference.com/w/cpp/container/vector/vector

//移动构造函数。用移动语义构造拥有 other 内容的容器。分配器通过属于 other 的分配器移动构造获得。移动后,保证 other 为 empty() 。
vector( vector&& other ); //(C++11 起) (C++17 前)
vector( vector&& other ) noexcept; //(C++17 起)

// 有分配器扩展的移动构造函数。以 alloc 为新容器的分配器,从 other 移动内容;若 alloc != other.get_allocator() ,则它导致逐元素移动。(该情况下,移动后不保证 other 为空)
vector( vector&& other, const Allocator& alloc ); //(C++11 起)

在 C++11中,std::vector有一个“移动构造函数”,对某个vector的右值引用可以单纯地从右值复制其内部C风格数组的指针到新的vector中,然后将右值中的指针置空。因为这个临时对象不会再被使用,没代码会再访问这个空指针,而且因为这个临时对象的内部指针是NULL,所以当这个临时对象离开作用域时它的内存也不会被释放掉。所以,这个操作不仅没有代价高昂的深拷贝, 还是安全的,对用户不可见的!

这个操作不需要数组的复制,而且空的临时对象的析构也不会销毁内存。返回vector临时对象的函数只需要返回std::vector<T>&&如果vector没有move 构造函数,那么就会调用常规拷贝构造函数。如果有,那么就会优先调用move构造函数,这能够避免大量的内存分配和内存拷贝操作。

右值引用不用对标准库之外的代码做任何改动就可以为已有代码带来性能上的提升. 返回值类型为std::vector<T>的函数返回了一个std::vector<T>类型的临时对象,为了使用移动构造不需要显示地将返回值类型改为std::vector<T>&&, 因为这样的临时对象会被自动当作右值引用。但是在c++03中, std::vector<T>没有移动构造函数, 带有const std::vector<T>&参数的拷贝构造会**被调用, 这会导致大量内存分配和拷贝动作.

出于安全考虑, 需要施加一些限制! 一个已命名的变量即使声明为右值,也不会被视为右值。想要获取一个右值,应该使用模板函数std::move<T>()。 右值引用也可以在特定情况下被修改, 主要是为了与移动构造函数一起使用!

由于”右值引用”这个词的自然语义,以及对”左值引用”(常规引用)这个词的修正, 右值引用可以让开发者提供完美的函数转发! 与可变参数模板结合时, 这个能力让模板函数能够完美地将参数转发给带有这些参数的另一个函数。这对构造函数的参数转发最为有用,创建一个能够根据特定的参数自动调用适当的构造函数的工厂函数.(没太懂…)

例如:

#include <string>
#include <iostream>
#include <vector>
using namespace std;

int main()
{
    string st = "hahaha";
    cout << &st <<endl;
    vector<string> vc ;
    vc.emplace_back(move(st));
    cout<<vc[0]<<endl;
    cout << &st <<endl;
    cout << st.empty() << endl;
    cout << (st.begin() == st.end()) << endl;
    cout << st << "xx" <<endl;
    if(!st.empty())
        cout<<st<<endl;

    return 0;
}

// 输出:
// 0x7fffd13c0860
// hahaha
// 0x7fffd13c0860
// 1
// 1
// xx // ==>move 后变成了empty(),string的内容也清空了,而对于double/int等普通类型,好像没变。。

1.2 constexpr – 泛化的常量表示式

1.3 对POD定义的修正

2. 核心语言构造期表现的加强

2.1 外部模板

3. 核心语言使用性的加强

3.1 初始化列表

3.2 统一的初始化

3.3 类型推导

3.4 基于范围的for循环

3.5 Lambda函数与表示式

3.6 回返类型后置的函数声明

3.7 对象构造的改良

3.8 显式虚函数重载

3.9 空指针

3.10 强类型枚举

3.11 角括号

3.12 显式类型转换子

3.13 模板的别名

3.14 无限制的unions

4. 核心语言能力的提升

4.1 可变参数模板

在 C++11 之前, 不论是类模板或是函数模板,都只能按其被声明时所指定的样子,接受一组数目固定的模板参数。C++11 加入新的表示法,允许任意个数,任意类型的模板参数,不必在定义时将参数的个数固定。

先写:

template<typename... Values> class tuple;

然后,模板类 tuple 的对象,能接受不限个数的 typename 作为它的模板形参(如下面的int/vector<int>/map<std::string, std::vector<int>>>)。

class tuple<int, std::vector<int>, std::map<std::string, std::vector<int>>> someInstanceName;

实参的个数也可以是0,所以class tuple<> someInstanceName这样的定义也是可以的。

若不希望产生实参个数为 0 的变长参数模板,则可以采用以下的定义:

template<typename First, typename... Rest> class tuple;

举个例子:

template <typename OutputType, typename...Args>
class baseClass {
    virtual int32_t process(OutputType *, const Args* ...) = 0;
};

实现一个子类如下,这样是没有问题的

class derivedClass: public baseClass<int, string, bool> {
    int32_t process(int* a, const string* b, const bool* c) override {
        return 0;
    }
};

int main()
{
    derivedClass xxx;
    return 0;
}

但如果把其中的一个const xxx*的指针去掉,如:

class derivedClass: public baseClass<int, string, bool> { 
    int32_t process(int* a, const string b, const bool* c) override {
        return 0;
    }
};
int main()
{
    derivedClass xxx;
    return 0;
}

因为我们写了override标识,所以直接就报错了,一方面是这个函数并非是我们想要重载的基类的纯虚函数,另一方面没有重载纯虚函数的子类是无法被实例化的~

./tmplt.h:18:13: error: 'int32_t derivedClass::process(int*, std::string, const bool*)' marked override, but does not override
     int32_t process(int* a, const string b, const bool* c) override {
             ^
./main.cpp: In function 'int main()':
./main.cpp:31:18: error: cannot declare variable 'xxx' to be of abstract type 'derivedClass'
     derivedClass xxx;
                  ^
In file included from ./main.cpp:5:0:
./tmplt.h:17:7: note:   because the following virtual functions are pure within 'derivedClass':
 class derivedClass: baseClass<int, string, bool> {
       ^
./tmplt.h:14:21: note:  int32_t baseClass<OutputType, Args>::process(OutputType*, const Args* ...) [with OutputType = int; Args = {std::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool}; int32_t = int]
     virtual int32_t process(OutputType *, const Args* ...) = 0;

而如果我们去掉override标识,报错就只剩没有实现纯虚函数的了,因为process函数被当做子类自己的新函数:

./main.cpp: In function 'int main()':
./main.cpp:31:18: error: cannot declare variable 'xxx' to be of abstract type 'derivedClass'
     derivedClass xxx;
                  ^
In file included from ./main.cpp:5:0:
./tmplt.h:15:7: note:   because the following virtual functions are pure within 'derivedClass':
 class derivedClass: baseClass<int, string, bool> {
       ^
./tmplt.h:12:21: note:  int32_t baseClass<OutputType, Args>::process(OutputType*, const Args* ...) [with OutputType = int; Args = {std::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool}; int32_t = int]
     virtual int32_t process(OutputType *, const Args* ...) = 0;

另外,在这个子类里,这个process函数可以自由地声明成private/protected/public,都行~

4.2 新的字符串字面值

4.3 用户定义字面量

4.4 多任务内存模型

4.5 thread-local的存储期限

4.6 使用或禁用对象的默认函数

4.7 long long int类型

4.8 静态assertion

4.9 允许sizeof运算符作用在类别的数据成员上,无须明确的对象

4.10 垃圾回收机制

5. C++标准程序库的变更

5.1 标准库组件上的升级

5.2 线程支持

5.3 多元组类型

5.4 散列表

5.5 正则表达式

5.6 通用智能指针

5.7 可扩展的随机数功能

5.8 包装引用

5.9 多态函数对象包装器

5.10 用于元编程的类型属性

5.11 用于计算函数对象回返类型的统一方法

5.12 iota 函数

X. 示例

shuffle

如下:

#include <random>

bool score_cmp(const RidTmpInfoPtr& first, const RidTmpInfoPtr& second) {
    return first->score > second->score;
}

bool func() {
    auto result_end = result.begin();
    std::advance(result_end, video_rec_buf.tmp_result_len);

    auto generator = std::mt19937(std::random_device()());
    std::exponential_distribution<float> distribution;
    for (auto& item: result) {
        item->score = -distribution(generator) * exp(-item->res_score);
    }
    std::sort(result.begin(), result_end, score_cmp);
}

带权随机采样

参考https://stackoverflow.com/questions/1761626/weighted-random-numbers

参考http://c.biancheng.net/view/646.html

#include <iostream>
#include <random>
#include <iterator>
#include <vector>
#include <ctime>
#include <type_traits>
#include <cassert>

int main()
{

    std::vector<std::string> idx_lst = {"a1", "a2", "a3", "a4"};
    std::vector<float> weight_lst = {10000, 2003, 8899, 3344};
    std::discrete_distribution<> dist(weight_lst.begin(), weight_lst.end());
    std::mt19937 gen((unsigned int) std::time(NULL));
    for (int i = 0; i < 200;++i) {
        int idx = static_cast<int>(dist(gen));
        std::cout << idx_lst[idx] << std::endl;
    }
    return 0;

}

均匀随机采样

#include <iostream>
#include <random>
#include <iterator>
#include <vector>
#include <ctime>
#include <type_traits>
#include <cassert>

int main()
{
    std::uniform_int_distribution<long long> dist(0, 1000000000);
    std::mt19937 gen((unsigned int) std::time(NULL));
    for (int i = 0; i < 200;++i) {
        int idx = static_cast<int>(dist(gen));
        std::cout << idx << std::endl;
    }
    return 0;

}

在一个unorder_map里随机抽取元素

https://stackoverflow.com/questions/27024269/select-random-element-in-an-unordered-map

极慢。。。。。

std::unordered_map<std::string, Edge> edges;
auto random_it = std::next(std::begin(edges), rand_between(0, edges.size()));

返回的是随机一个非空的edge

更复杂一点:

#include <iostream>
#include <random>
#include <iterator>
#include <vector>
#include <ctime>
#include <type_traits>
#include <cassert>
#include <map>
#include <string>
#include <memory>
#include <unordered_map>


int main()
{
    std::unordered_map<uint64_t, std::string> x_map;
    std::shared_ptr<std::unordered_map<uint64_t, std::string> > ptr_x;
    for (int i = 0; i < 200;++i) {
        x_map.emplace(i, "aa");
    }
    ptr_x = std::make_shared<std::unordered_map<uint64_t, std::string> >(x_map);
    
    std::uniform_int_distribution<long long> dist(0, 199);
    std::mt19937 gen((unsigned int) std::time(NULL));
    for (int i = 0; i < 2000;++i) {
        int idx = static_cast<int>(dist(gen));
        std::cout << "idx" << idx << std::endl;
        auto it = std::next(ptr_x->begin(), idx);
        std::cout << it->first << std::endl;
    }
    return 0;

}

!!!快得多的方法:

建个list,size和map一样大,把map的key扔到这个list里去

然后随机一个index,取出这个list对应的元素,再去map里get


原创文章,转载请注明出处!
本文链接:http://daiwk.github.io/posts/knowledge-c++11.html
上篇: deep & cross network for ad click predictions
下篇: tagspace

comment here..