Modern Cpp

介绍Cpp 11/14/17 的一些高频的 新的语法和特性。

使用Modern Cpp可使得开发更加便捷,可读性更好,代码看起来更加精简。

在Cpp 11 为我们带来了很多的新特性,使得其是Cpp发展中具有里程碑的版本。

统一的类成员初始化方法和std::initializer_list<T>

在Cpp11 前,需要初始化类的某些成员会非常的繁琐

例如:

//类中具有一个int 数组。
class CppOlder{
public:
    CppOlder(){
        m_array[0]=0;
        m_array[1]=1;
        m_array[2]=2;
        m_array[3]=3;
    }
private:
    int m_array[4];
};

但是在Cpp11可以这样写

class ModernCpp {
public:
    ModernCpp():m_array{1,2,3,4} {
    }

private:
    int m_array[4];
};

//或者这样写
//虽然可以在定义的地方可以初始化,但是建议还是在构造函数处初始化,方便维护。
class ModernCpp {
public:
    ModernCpp(){
    }

private:
    int m_array[4]{1,2,3,4};
};

自己实现的类,如何使用这种语法?

通过std::initializer_list<T>

class ModernCpp {
public:
    ModernCpp(std::initializer_list<int> list):m_array{1,2,3,4}{
        m_vec.insert(m_vec.end(),list.begin(),list.end());
    }
    void append(std::initializer_list<int> list){
        m_vec.insert(m_vec.end(),list.begin(),list.end());
    }
private:
    std::vector<int> m_vec;
    int m_array[4];
};

// ModernCpp modernCpp{1,2,3,4};
// modernCpp.append({1,2,3,4});
// m_vec为 1 2 3 4 1 2 3 4

一个稍微复杂,但看起来很有用的示例

enum class JsonType {
    JsonInt = 0,
    JsonString,
    JsonDouble,
    JsonFloat
};

struct JsonNode {
    JsonNode(const char *key, const char *value) : 
       m_type(JsonType::JsonString),
       m_key(key),
       m_value(value)
    {
        std::cout << "JsonString" << std::endl;
    }

    JsonNode(const char *key, const double value) : 
        m_type(JsonType::JsonString),
        m_key(key),
        m_value(std::to_string(value)) 
    {
        std::cout << "JsonString" << std::endl;
    }

    JsonType m_type;
    std::string m_key;
    std::string m_value;
};

class json {
public:
    static json &array(std::initializer_list<JsonNode> nodeList) {
        m_json.m_jsonNodes.clear();
        m_json.m_jsonNodes.insert(m_json.m_jsonNodes.end(), nodeList.begin(), nodeList.end());
        return m_json;
    }

private:
    std::vector<JsonNode> m_jsonNodes;
    static json m_json;
};

json json::m_json;


//main.cpp
json array = json::array({
         {"hello", "world"},
         {"hello1", "world"},
         {"hello2", "world"}
});

final、override、=delete 、=default

这些关键字是Cpp11新增加的一些比较有意义的关键字和语法。

final

被final修饰的类表明,这个类无法被继承。

当我们试图去继承这个类的话,会出现编译错误。

class TestFinal final{
public:
    TestFinal() = default;
    ~TestFinal() = default;

};

class TestFinal1:public TestFinal{

};
//编译错误
//error:: 25: base 'TestFinal' is marked 'final'

override

当一个类成员函数被virtual关键字修饰的时候,表明其子类重写这个函数。

override关键字,用于告诉编译器,这个类成员函数是重写父类的方法,编译器会对其进行相应的检查。

在Cpp11前,在子类中重写父类的方法时,无论是否添加了virtual关键字,都无法显示的表面子类是否重写了该方法。

因为,我们在重写方法是,可能将其函数签名写错,这时编译器并不会检查这类错误,因为子类也可以是另一个子类的父类。

如果函数签名写错了,就定义了一个新的函数出来,而不是重写父类的方法。

class TestOverride{
public:
    virtual void helloWorld(std::string str){ std::cout<<str<<std::endl;}
};

class TestOverrideInherit:public TestOverride{
public:
    void helloWorld() override{}
};
//编译错误,函数签名正确
//error: non-virtual member function marked 'override' hides virtual member function

//改成这样后,编译通过。
class TestOverrideInherit:public TestOverride{
public:
    void helloWorld(std::string str) override{}
};

=default

当我们定义了一个类,如果我们没有编写构造函数,拷贝构造,析构函数,operator=,编译器会帮我们生成默认的函数。

但是当我们只写出来声明时,编译就会报错。当我们写了一个带有参数的构造,那么编译器就不会帮我们生成默认的函数。

class TestDefault{  
};

int main(){
	TestDefault td;
}
//以上代码是可以编译通过的。



class TestDefault{
public:
    TestDefault(int i){};
};

int main(){
	TestDefault td;
}

//当我们添加了一个有参的构造函数时,编译器不会再帮我们生成默认的构造函数了。
//编译错误:
//error: no matching constructor for initialization of 'TestDefault'


class TestDefault{
public:
    TestDefault() = default;
    TestDefault(int i){};
};

int main(){
	TestDefault td;
}
//显示的告诉编译器帮我们生成默认的构造函数。
//在一些不需要进行大量初始化动作的类中,这样写可以简化代码。

=delete

告诉编译器不要帮我生成默认的函数。

在Cpp11以前,我们想要实现一个类无法被拷贝,需要将其拷贝构造函数、operator=()都设置为私有的函数这样就实现了禁止拷贝。

class TestDelete{
public:
    TestDelete()  = default;
    TestDelete(const TestDelete& other) = delete;
    TestDelete& operator=(const TestDelete& other) = delete;
};


int main(){
 	TestDelete tDelete;
    TestDelete tDelete1 = tDelete;
    TestDelete tDelete2(tDelete);
}

//编译错误:
//error: call to deleted constructor of 'TestDelete'

auto关键字、Range-based循环

这两个新的特性和语法,在平时的开发中很常用。

auto关键字

Cpp11中的auto和Cpp98 auto 完全不一样。

在Cpp98中,auto关键字表明该变量具有自动生命周期,但是完全是多余的,因为默认变量就具有自动生命周期。

在Cpp11中,使用auto关键字来自动推导出变量的类型。

auto testAutoInt = 1;
auto testAutoDouble = 1.1;
auto testChar = 'c';
auto p = new ClassA();

但是在一些简单的数据类型,尽量还是不要使用auto。

尽量使用在一些复杂的数据类型中,例如 如下代码:

std::vector<std::string> vec;
std::vector<std::string>::iterator  iter = vec.begin();
auto iter = vec.begin(); //使用auto看起来简洁很多。

但是在使用auto时需要注意:

  1. 定义变量时必须初始化。
  2. 如果表达式是引用,这将除引用语义
      int a = 12;
      int &b = a;
      auto c = b;
      b = 20;
      std::cout<<a<<std::endl; //a = 20;
  3. 如果表达式是为const,这将去除const语义
      const int a=2;
      auto b = a;
      b = 3;
      std::cout<<a<<std::endl; //a = 3;
  4. 使用auto时,添加&符号,则保留了const语义
      const int a = 2;
      auto &b = a;
      b = 3; //error:Cannot assign to variable 'b' with const-qualified type 'const int &'

Range-based 循环

在没有Range-based循环的语法前,大多数遍历的时候只能通过下标去完成遍历,或者是迭代器的增加去完成遍历。

int a[5]={1,2,3,4,5}; //Cpp11 列表初始化。
for (int i = 0; i < 5; ++i) {
    std::cout<<a[i]<<std::endl;
}

//通过迭代器遍历
std::vector<int> vec{1, 2, 3, 4, 5};
std::vector<int>::iterator iter;
for (iter = vec.begin(); iter != vec.end(); iter++) {
    std::cout << *iter << std::endl;
}

通过Range-based循环,可以很大程度上简化上面的代码

std::vector<int> vec{1, 2, 3, 4, 5};
for (auto item : vec) {
    std::cout << *iter << std::endl;
}

//item 是vec中元素的一个副本。
for (auto &item : vec) {    //添加引用就可以修改对应的元素。
    std::cout << *iter << std::endl;
}

for (const auto &item : vec) {    //添加引用可以不拷贝元素,并通过const来避免被修改。
    std::cout << *iter << std::endl;
}

Cpp17 结构化绑定

使用结构化绑定,更加方便的返回多值,并且不用定义结构体。

使得代码看起来更简洁明了,开发也更加方便。

std::tuple<std::string, int> Cpp17StructuredBinding() {
    return std::tuple<std::string, int>("小明", 18);
}
auto [name, age] = ModernCpp::Cpp17StructuredBinding();

使用限制:

  • 无法使用constexpr 修饰
      constexpr auto [name, age] = ModernCpp::Cpp17StructuredBinding(); //编译错误
  • 无法使用static 修饰
      static auto [name, age] = ModernCpp::Cpp17StructuredBinding(); //编译错误

STL 容器新增加的实用方法

STL中新增加的一些,提升效率的方法。

emplace系列函数