C++11特性

很多以前存在的组件已经放入了STL

比如regex,tuple等

需要注意的比较重要的特性

模板多函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

void print() {}
template <typename T, typename... Types>
void print(const T& fristArg, const Types&... args) {
cout << fristArg << endl;
cout << "当前还没执行的参数" << sizeof...(args) << endl;
print(args...);
}

int main() {
print(12, "hellp", "skdjskd");
return 0;
}

这三个点实际上就是一个关键词

1
...

表示一个pack,在不同的位置有不同的意思和用法

1
2
3
4
5
6
template<typename T, typename... Types>
//表示多个类型模板参数包
const Types&... args
//表示多个函数参数类型包
args...
//表示函数参数包

很方便的实现递归的操作比如tuple的实现,实现同名不同类型的成员等

接下来第二个例子

了解了新特性后打算重写printf(c的输出函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void myprintf(const char* s) {
while (*s) {
if (*s == '%' && *(++s) != '%') {
throw runtime_error("invalid format string");
}
cout << *s++;
}
}
template <typename T, typename... Args>
void myprintf(const char* s, T value, Args... args) {
while (*s) {
if (*s == '%' && *(++s) != '%') {
cout << value;
printf(++s, args...);
return;
}
cout << *s++;
}
throw logic_error("extra arguments");
}

int main(void) {
int* pi = new int;
myprintf("嘿嘿%d真不错%s %p %f \n", 12, "heihie", pi, 3.1415926);
return 0;
}

第三例子:实现一个Max函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//如果只是单纯实现,参数类型相同时
max({213,234,213,24,656,23});
//用不到variadic template,按照标准库的默认处理便好

//当传入的形式发生变化
max(213,(int)23,(others)234,Person(),234);
//就需要使用到variadic template 来进行函数的实现

template<typename T>
T mymax(const T& n){
return n;
}
template<typename T,typename... Args>
T mymax(const T& n,Args... args){
return std:max(n, mymax(args...));
}

tuple 元组

一般用于,返回值有多个类型

第四个例子

tuple的使用

make_tuple的使用

递归,分节递归,递归的创建

第五个例子

递归的继承

同样是从tuple源代码来看,简易版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<typename... Balues>class tuple;
templte<> class tuple<>{};
template<typename Head, typename... Tail>
class tuple<Head, Tail...>:private tuple<Tail...>
{
typedef tuple<Tail...>inherited;
public:
tuple(){}
tuple(Head v, Tail... vtail):m_head(v), inherited(vtail...){}
typename Head::type head() {return m_head;}//error
//拿出Head的类型,但是有些类型并没有type (比如并不是STL中的类型)
//这里就出现问题了,如果使用了别的类型,将会出现编译错误,然而这里使用decltype就可以找出来类型
auto head()->decltype(m_head){return m_head;}
//然而,那我们为什么不直接使用Head的类型呢?
Head head(){return m_head;}
inherited& tail(){return *this;}
protected:
Head m_head;
};

第六例子

用于复合,tuple

Spaces in Template Expressions

模板嵌套空格省略

nullptr and std::nullptr_t

使用nullptr 代替 函数参数中NULL/0,作用就是更好的区分开空指针与数值(int)0 的区别

定义了一个新的指针类型nullptr_t

Automatic Type Deduction with auto

auto 推导类型,是一种语法糖

一般在定义迭代器的时候,类型将会写很长,很复杂,这里我我们就可以使用auto 来自动推导类型

Uniform Initialization

统一版本的对象初始化

使用{}态构造初始化

{}构建出std::initializer_list 这个构造出来的对象关联着一个array,实际上关联,只有一个迭代器

在这个容器中逐一调用构造函数或者原本就可以多个进行初始化

initializer_lists

编译器可以调用改对象的私有构造

需要注意的是

1
2
int a = 10 / 3 ; // 3
int a {10/3}; // 3 warning

explicit

关键字

在C++11之前

explicit for ctors taking one argument

只会调用一次,主要调用在构造函数上并且构造函数中只有一个参数

C++11后

explicit for ctors taking more than one argument

也只会调用一次,但是构造函数可以不只一个参数

range-based for statement

for循环的一种简单写法

1
2
3
4
5
6
7
for( decl : coll ) {
statement
}

for (int& nums: {2, 3, 4, 5} ){
cout << elem << endl;
}

=delete , = default

通常写了一个构造函数之后,编译器就不回给你默认的构造函数,在构造函数之后添加=default 后就可以获得并使用default ctor

=delete 就顾名思义,删除为零

一个类中如果包含指针,基本必须都需要自己主动定义Big-Three (构造拷贝构造析构三大件)

No-Copy and Private-Copy

Private- Copy or No-Copy 的典型应用就是单例模式single

Alias Template

化名模板(别名)

比c的宏有更简单的用法(当然也是有代价的)

关键字:using

1
2
3
4
template <typename T>
using Vec = std::vector<T, MyAlloc<T>>
//is epuivalent to
Vec<int> coll;

模板的特化问题,尚未解决,不能对别名做特化,只能原原本本的模板本身进行特化操作

模板模板参数

template template parameter

模板的高深的使用方式。

但是注意的是只能模板模板参数,不能模板模板参数的模板参数*2,编译器只能推导一次

编译器第一次开始编译推导确实是能通过的,但是,需要注意的是,并不知道的模板模板参数的绑定究竟是什么类型,实际在运行的时候如果还是需要编译器进行推导还是会出现问题。(编译器并没有想象的这么聪明

所以常常Alias template 和模板模板参数进行混合使用,从而达到模板模板参数的模板参数的效果

现在我们模拟一个实现场景

已知一个数据组,我需要将其进行测试,满足以下调用效果,作用是检测是否该数据组是否能够放入不同容器并且进行数据搬移move。

1
2
3
Test(vector, mystring);
//mtstring 是我自定义的数据集
//vector 是我进行测试的容器

起初写了一个测试函数

1
2
3
4
5
6
7
8
9
10
template<typename Container, typename T>
void Test(Container cntr, T elem){
Container<T> c;
for(long i = 0; i < SIZE; ++i)
c.insert(c.end(), T());
output_static_data(T());
Container<T> c1(c);
Container<T> c2(std::move(c));
c1.swap(C2);
}

然而并不行,在实际调用时编译出现错误,错误非常明显,Container并不是一个模板

这时,就可以想到了,模板模板参数(不过呢,为了实现过程在更曲折一点,假装没想到模板模板参数。

想到了一个下位替代,使用函数模板和迭代器和萃取器(traits)

我提前就将数据放好在容器中作为参数,再进行测试

1
2
3
4
5
6
7
8
9
10
template<typename Container>
void Test(Container c){
typedef typename iterator_taits <typename Container::iterator>::value_type Valtype;
for(long i = 0; i < SIZE; ++i)
c.insert(c.end(), Valtype());
output_static_data(*(c.begin()));//静态数据输出测试
Container<T> c1(c);
Container<T> c2(std::move(c));
c1.swap(C2);
}

然而测试函数的调用方式发生了改变,尽管作用一致

1
Test(vector<mystring>());

感觉还是没有一开始设想的调用方式好,假如不想使用迭代器和trait怎么办呢,假如所使用的容器是自己设计的并没有遵循标准库的设计,是不是就无法使用这个测试函数了。

这个时候,终于想起来要使用模板模板参数了(迫真

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename T,template <typename U> class Container> 
class XCls
{
private:
Container<T> c;
public:
XCls(){
for(long i = 0; i < SIZE; ++i)
c.insert(c.end(), T());
output_static_data(T());//静态数据输出测试
Container<T> c1(c);
Container<T> c2(std::move(c));
c1.swap(c2);
}
}

这样的类测试编译通过了,再进行调用时,却出现了错误

1
XCls<mystring, vector> c1;

错误原因是:第二个实参的类型不符合,深排原因,标准库中vector的实现有两个模板参数(需要指定适配器),然而,模板模板参数中需要特别注意的点来了,即使模板中的模板参数中第二个参数有默认值时,编译器并不能进行推导。

这个时候可以使用别名参数进行指定参数到底是什么,来帮助编译器进行推导

1
2
template <typename T> 
using Vec = vector<T, allocator<T>>;

需要注意的事,该化名并不能在类中或者function body中声明。

最终函数的调用方式将会是

1
XClS<string, Vec> mystring;

type alias

类型别名

1
2
3
using func = void(*)(int ,int);
//simliar to typedef
typedef void(*func)(int, int);

实际上我们在使用标准库的容器时,使用的string 实际就是using string = std:: basic_string \

noexcept 不异常

宣告这个函数不会出现异常

1
2
3
void func () noexcept {
...
}

异常是个大学问,此处不谈

在使用成长性容器时(vector 或deque(实际上内部是靠着vector实现)),会发生 内存越界 memeory reallocation

在11特性中,noexcept的作用是声明 vector的调用不会抛出异常,而是有一个自己设定的处理方法(move)

总结就是,在设计一个类时,抛异常是非常致命的,设计到进行搬移的时候

override 重写

一般应用在虚函数之上,告诉编译器该函数重写:避免和重载和声明定义新的函数发生混乱

final 最终

两种比较常见用法,不过效果都是表示不可重写,与override对立

1
struct Basel final{};

decltype

使用方式类似 sizeof() and typeof();

类型的表达式的类型,一般用来定义一个type

1
2
3
4
//得到container_obj的类型
decltype(container_obj)::value_type elem;
//similar to typeof
map<string,float>::value_type elem;

有三种应用

第一种:用于声明返回类型

1
2
3
4
5
6
7
8
template<typename T1, typename T2>
decltype(x + y)add(T1 x, T2 y);
//声明x+y 的结果的类型实际是add这个函数的结果的返回类型
//比如add函数的返回值是int
//作用是提前做好声明,保证编译器不会报错
//不过这样其实是编译失败的,会出现x和y未定义
//所以这里就需要使用auto来辅助进行修改
auto add(T1 x, T2 y) -> decltype( x + y );

需要注意的是这是一种新的返回类型的指定方式,与lanbdas的表达形式相似

第二种:in metaprogranmming

1
typedef typename decltype(container_obj):: iterator iType;

在lambda没有使用之前,设定一个仿函数需要定义一个类,按照传统的方式来定义

使用仿函数时作谓词时

1
std::set<string, functor> myset;

使用lambda时使用方式则变更为

这就是第三种

传递lambda的类型

1
2
3
4
5
auto cmp = [](const Person& p1, const Person& p2){
......
};

std::set<Person, decltype(cmp)> myset(cmp)

原因是因为我们手上往往只有objec 而没有type,所以需要借助decltpye来取出类型(类),在set的众多的构造函数中,有着一个可以使用lambda的接口,需要将lambda传给构造函数,否则调用的依旧是默认的规则(构造函数),然而lambda并没有构造函数,所以将会编译失败。

lambdas

定义的一个内联函数

definition of inline functionality,similar to like functions

lambda introducer

这个中括号有说法,除了平常常用对外部的变量的值和引用以外还有更多深入的用法

1
2
3
4
//表示外部传的值可以发生变化,(可读不可写) mutable
//传引用时则可以加也可以省略
//比较完整的lambdas写法
[...](...)mutable throwSpec ->retType{...}

由于lambdas默认是内联函数 效率实际上还是很高的

更多的使用方式需要单独开一章文更加深入了解

右值引用与左值引用

左值其实就是一个变量,这是咱的基本的变量的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
int a = 1;
int b = 2;
int c = b;
int d = a;
//以上都是左值
a + b = 42;
//a + b 就是右值了,但是这样是人ERROR的
string s1("sadas");
string s2("sdfad");
s1 + s2 = s2;
//然而在标准库中 string 是可以通过编译的
string() = "sdasdsf";
//而临时变量,也是一个右值,并且编译通过

而右值,是不可以出现在左边的,在C++11以前,是没有右值引用的概念的

1
2
int foo(){retuen 6;}
int *p = &foo();//ERROR

在C++ 11以后,出现了新的语法,来声明,告诉编译器,这是一个右值引用,加入noexpect修饰,保证不会报错

一般,在拷贝时,深拷贝与浅拷贝,实际上都是左值copy,而遇到成长性的容器时,在保证效率需要使用到右值拷贝来进行效率上的优化避免出现不必要的拷贝,只需要把指针拷贝就好,于是就出现了 steal 的构造函数 move ctor,但是这个事情是相当危险的,毕竟是两个指针指向同一个内存块,所以move 函数的搬移 就是将左值转换为右值, 当拷贝完成之后,就需要打断指针指向,所以,被move的值,就不能再进行使用,从而保证安全。

注意,以上的move都是 std::move

1
2
3
4
Mystring(Mystring && str)noexcept:initalization list{
...
}
//&& 声明,告诉编译器传进来的就是右值,而左值一样也可以使用move函数 声明为右值。

设计一个MyString和一个无move的功能的MyString来实现对照

设计多个构造函数,并对每次调用进行计数,测试搬移构造与拷贝构造的效率

在测试vector和deque等成长性容器可以发现,有move与无move的copy效率差别是巨大的,而对于list与红黑树等影响不是非常大

1
2
3
4
5
6
//传统的左值传入,进行的拷贝
M c1(c);
//右值传入,进行的其实是指针的交换。
M c2(std::move(c));
//临时变量,也是右值传入
M c3(M());

再次声明:当拷贝完成之后,就需要打断指针指向,所以,被move的值,就不能再进行使用,从而保证安全。