关于C++11
C++11 is the second major version of C++ and the most important update since C++98. A large number of changes were introduced to both standardize existing practices and improve the abstractions available to the C++ programmers.
Before it was finally approved by ISO on 12 August 2011, the name ‘C++0x’ was used because it was expected to be published before 2010. It took 8 years between C++03 and C++11, so it has become the longest interval between versions so far. Since then, currently, C++ updates every 3 years regularly.
本文大纲
- auto
- decltype
- final and override
- trailing return type
- rvalue reference
- Move constructor
- Move assign constructor
- constexpr
- brace-or-equal initializers
- nullptr
- long long
- delegating and inherited constructors
- using
- variadic templates
- lambda
Core Language Features
1. auto
auto: placeholder type specifiers 占位限定符
作用为:在初始化的时候进行类型推导, 但是其遵循模板参数推导
因此在推导的类似的时候只是基本类型,所以需要const 或者 & 等modier一起参与推导。
auto x = expr;
auto& x = expr;
const auto& x = expr
注意: auto有一点与template模板推导不同
auto x = {1,2,3};
这个auto会进行二次推导,首先推导成initiallizer_list, 然后在对initialize的T进行推导。而模板无法对{}的表达式进行直接推导
关于返回值推导: C++11不支持返回值推导
auto func(auto a, auto b)
这个在C++11是不被允许的,auto作为参数和返回值不能进行推导
但是注意:在C++14里,该语句将被允许
2. decltype
跟auto不同,auto是由通过初始化的值进行类型推导,decltype则通过entity或者表达式进行推导。主要是用于在模板编程中,需要通过表达式进行类型推导。
还有一点不同就是auto是按照模板进行推导,需要搭配&,const这类modifier进行推导,但是decltype则不需要,他会根据参数类型进行推导 不同的类型
decltype(entity)
decltype(expression)
关于这个推导的参数分为两种。
- 没有小括号
- 如果参数表达式类型为 xvalue, 则推导的类型为T&&
- 如果参数表达式类型为 lvalue,则推导的类型为T&
- 如果参数表达式类型为 prvalue,则推导类型为T
- 带有小括号
-
带有小括号,则一概将参数看作常规的lvalue
3. final and override
这两个限定符一定要直接加载virt-specifier-seq的后面
final
final限定符,表示这个虚函数/或者这个类不能被派生类进行override
struct Base
{
virtual void foo();
};
struct A : Base
{
void foo() final; // Base::foo is overridden and A::foo is the final override
void bar() final; // Error: bar cannot be final as it is non-virtual
};
struct B final : A // struct B is final
{
void foo() override; // Error: foo cannot be overridden as it is final in A
};
struct C : B {}; // Error: B is final
override
就是表明该函数显示的重写了基类的虚函数。
加上关键字,一旦出现函数名写错,并没有对基类函数进行重载,编译器会报错。作为编程让编译器检查的手段
class A
{
public:
virtual void print(){
std::cout << "hello" << std::endl;
}
};
class B final: A
{
public:
void print(int a) override{ //error: 'void B::print(int)' marked 'override', but does not override
std::cout << "hi "<< a << std::endl;
}
};
4 trailing return type
尾置返回类型(trailing return type) 跟在形参后面并以 -> 符号开头,
用来解决返回类型依赖于参数的问题。
cpp reference 给出了两个例子
//由于是模板传参,返回值不定,因此需要进行推导, 尾后置类型->来简化
template<class T, class U> auto add(T t, U u) -> decltype(t + u);
// 简化返回类型
auto fpif(int)->int(*)(int)
5 右值引用
Rvalue References: 右值引用是用来扩展临时变量的生命周期
(const的左值引用也可以扩展临时变量的声明周期)
&& attr(optional) declarator
这里对比左值引用:
Lvalue References: 左值引用是为了对现存对象进行一个别名操作。
注意,只要是引用就一定要进行初始化
#include <iostream>
#include <string>
int main() {
std::string s1 = "Test";
// std::string&& r1 = s1; // error: can't bind to lvalue
const std::string& r2 = s1 + s1; // okay: lvalue reference to const extends lifetime
// r2 += "Test"; // error: can't modify through reference to const
std::string&& r3 = s1 + s1; // okay: rvalue reference extends lifetime
r3 += "Test"; // okay: can modify through reference to non-const
std::cout << r3 << '\n';
}
附:
简单介绍: 左值,纯右值,将亡值
左值(lvalue):可以取地址的,有名字的变量
纯右值(prvalue):字面量,无名字的临时变量
将亡值(xvalue):其实属于 泛左值 和 右值 两个概念。
用左值去初始化对象或为对象赋值时,调用拷贝构造函数或赋值构造函数。
用右值去初始化对象或赋值时,调用的是移动构造函数和移动赋值值函数。
将亡值可以理解为通过移动构造其他变量内存空间的方式的值。
6 Move constructors 移动构造
//1) Typical declaration of a move constructor.
class-name ( class-name && );
// 2) Forcing a move constructor to be generated by the compiler.
class-name ( class-name && ) = default;
class-name ( class-name && ) = delete;
与拷贝构造函数,传参为&&右值引用,移动构造一般是用右值(xvalue or prvalue)进行初始化的时候进行调用。移动构造函数会直接使用临时变量的资源,从而延长其生命周期,减少进行拷贝的开销。
三种初始化的方法
1 初始化: T a = std::move(b);
2 函数传参: f(std::move(a))
3 函数返回: T() 函数中 return a; 但a存在移动构造函数的时候,会进行调用
7. Move assign operator
移动赋值操作
class-name & class-name :: operator= ( class-name && )
可以发现与移动构造相同,都是重载操作符=,移动赋值与拷贝赋值的关系即是参数为右值引用进行传参,从而减少拷贝时间。
8. constexpr specifier
constexpr 关键字用来声明 函数或者变量可以在编译期进行执行
- constexpr修饰变量,则表明该变量为const,与const关键字相同
- constexpr修饰函数,则表明该函数为inline,同时在C++11标准中,该函数传参和返回值必须为字面量,而且C++11中constexpr修饰的函数只能使用递归而非循环,C++14中可以使用循环但是必须使用局部变量。
关于使用costexpr: 来看一个例子
//由于constexpr修饰,则复杂度只为O(n),相当于迭代计算,在编译过程中会进行将数据进行存储,但递归复杂度过高时,会在编译耗时较长,也有可能出现栈溢出
constexpr long int fib1(int n) {
return (n <= 1)? n : fib1(n-1) + fib1(n-2); //只能包含一个retrun语句
}
//在运行时进行递归,复杂度为O(2^n)
long int fib2(int n){
return (n <= 1)? n : fib2(n-1) + fib2(n-2);
}
9 brace-or-equal initializers
C++11统一了初始化,列表初始化即用大括号{ }进行初始化,分为直接初始化 和 列表拷贝初始化
// 列表初始化
T object{arg1, arg2, ...}
// 拷贝列表初始化
T object = {arg1, arg2, ....}
10. nullptr
nullptr类型用来规范NULL 这个宏定义。但使用空指针的时候,尽量使用nullptr.
NULL在有些编译器直接 #define NULL 0, 有些是#define NULL (void*) 0
在重载的时候,会造成混乱,比如下面的例子
void print(char* ptr){
std::cout << "char* parser" << std::endl;
}
void print(int val){
std::cout << "int parser" << std::endl;
}
上述代码但调用
print(NULL)的时候,会出现混乱的重载。我在g++ 8.1, c++14中测试发现该语句会直接保持,显示error: call of overloaded ‘print(NULL)’ is ambiguous
11. long long
C++ 11增加了longlong的表示类型,定义64bit 宽的数据类型。
12. 委托构造和继承构造 delegating and inherited constructors
委托构造:
比如有下面的例子:
class Foo
{
public:
Foo(char x, int y) {}
Foo(int y) : Foo('a', y) {} // Foo(int) delegates to Foo(char, int)
};
F(int y) 要先调用Foo(char x, int y), 用列表初始化的方式减少重复代码。
继承构造:
如果使用了 using Base:Base 这句,那么所以基类的构造函数都会被在派生类重载
If the using-declaration refers to a constructor of a direct base of the class being defined (e.g. using Base::Base;), all constructors of that base (ignoring member access) are made visible to overload resolution when initializing the derived class.
class Base
{
public:
Base(int i) :m_i(i) {}
Base(int i, double j) :m_i(i), m_j(j) {}
Base(int i, double j, string k) :m_i(i), m_j(j), m_k(k) {}
...
};
class Child : public Base
{
public:
using Base::Base; // 将会复用基类所有的构造函数
};
13. 类型别名,using
C++11增加了用using表示别名的方法,本质上与typedef 关键字非常像,但是using支持定义模板的别名,而typedef则不支持。对于复杂的名称,using会比typedef更简洁。
函数指针
// 为函数指针取别名
typedef void(*fun)(int x, int y) //定义了一个函数指针,别名为fun
using fun = void(*)(int x, int y) //using 表示
// 为模板取别名
template<typename T>
using m = map<int, T>
14. 变长模板
变长模板是由打包参数进行实现,
符号为"…"表示
15. lambda 表达式
在C++11中,lambda的规范如下。
[ captures ] ( params ) specs requires(optional) { body }
其中[captures] 为捕获列表,params为参数列表,opt为函数选项
lambda capture
capture列表中可以包括零个或者多个外部的捕获变量,capture list定义外部变量可以被匿名函数体内所访问
- & (隐式的捕获外部所有变量的引用, 引用传递)
- = (隐式的捕获外部所有变量的值, 值传递)
关于其他捕获形式,可在cppreference中查询https://en.cppreference.com/w/cpp/language/lambda