c++新特性之default,delete

用法

=default 表示使用编译器为我们提供的实现,=delete表示删除函数,不实现。

=default =delete一般只能用在编译器为我们生成的函数上,比如构造函数,拷贝构造,析沟函数(不要使用=delete),赋值等。

=delete可以用在普通成员函数,但是没有意义。=delete修饰析构函数时,无法进行释放对象,因此无法生成临时变量,无法进行delete,也无法当作父类。

下面进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Complex
{
int real,image;
Complex(int a, int b=0) : real(a), image(b)
{
}
void print() = delete; //oK
//void print2() = default; //errror
~Complex()=delete;
};

int main()
{
//Complex c; //error
Complex *s = new Complex(12, 5);
//s->print(); //error
// delete s;//error
return 0;
}

在c++11之前如果我们自行定义了一个构造函数,那么编译器就不会生成一个默认的构造函数。
在c++11中我们可以加上=default,获得并使用默认的构造函数。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
class zoo 
{
public:
Zoo(int i1,int i2):d1(i1),d2(i2){}
Zoo() = default;
Zoo(const Zoo&) = delete;
Zoo(Zoo&&) = default;
Zoo& operator=(const Zoo&)=default;
Zoo& operator=(const Zoo&&)= delete;
virtual ~Zoo(){}
private:
int d1,d2;
}

其他

空类什么时候不空?

我们定义一个空类,编译器就会为它声明一个拷贝构造,一个拷贝赋值,和析构,构造函数。这些函数都是public且inline.

1
class Empty{};

我们定义了Empty,当我们没有调用构造,析构等函数时,编译器是不会给我们合成的。

唯有这些函数被调用时,它们才会被编译器合成。

1
2
3
4
5
{
Empty e1; //调用了构造析构
Empty e2(e1);//拷贝构造析构
e2 = e1;//拷贝赋值
}

常见用法noCopy

将拷贝相关的代码delete,实现nocopy 功能
M

1
2
3
4
5
6
7
8
class NoCopy
{
public:
NoCopy() = default;
NoCopy(const NoCopy&)=delete;
NoCopy& operator=(const NoCopy&)=delete;
~NoCopy() = default;
}

当我们需要实现这个功能时只需要继承 NoCopy就可以了。

当然实现NoCopy还有其他方式,将拷贝构造,拷贝赋值的属性变为private。这种方式对于友元是无效的。

如下是boost::noncopyable实现。

1
2
3
4
5
6
7
8
9
class noncopyable
{
protected:
noncopyable(){}
~noncopyable(){}
private:
noncopyable(const noncopyable&){}
const noncopyable& operator=(const noncopyable&){}
}

c++新特性之explicit

explicit for ctors taking one argument

我们看一组例子 有两个复数类 Complex 与Complex2 他们的唯一区别就是

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
27
28
29
30
31
32
33
34
35
struct Complex
{
int real,image;
Complex(int a, int b=0) : real(a), image(b)
{
}
Complex operator+(const Complex &x)
{
return Complex(real + x.real, image + x.image);
}
};

struct Complex2
{
int real,image;
explicit Complex2(int a, int b=0) : real(a), image(b)
{
}
Complex2 operator+(const Complex2 &x)
{
return Complex(real + x.real, image + x.image);
}
};
```

```cpp
int main()
{
Complex c1(12,4);
Complex c2 = c1 + 5; //ok 5被转隐式转换为Complex

Complex2 c3(12,4);
Complex2 c4 = c3 +5; //error
return 0;
}

explicit for ctors taking more than one argument (c++11)

c++新特性之初始化列表

initializer Lists

用途:

用来设置初值

1
2
3
4
int i; //undefined 
int j{} // 0
int *p;//uhdefined
int *q{} //nullptr

需要注意的 narrowing initializations
当有数据丢失的风险时会报错 或者 warining 下面是在gcc7.5.0上测试的结果

1
2
3
4
5
6
7
8
int x1(5.3);//OK 
int x2 = 5.3;//OK
int x3{5.3}; //error: narrowing conversion of ‘5.2999999999999998e+0’ from ‘double’ to ‘int’ inside { } [-Wnarrowing]
int x4 = {5.3}; // error: narrowing conversion of ‘5.2999999999999998e+0’ from ‘double’ to ‘int’ inside { } [-Wnarrowing]
char c1 {7}; //OK
char c2{257}; //error: narrowing conversion of ‘257’ from ‘int’ to ‘char’ inside { } [-Wnarrowing]
std::vector<int> v1 {1,2,3,4,5};//OK
std::vector<int> v2{1,2.3,4,4.6};/ error: narrowing conversion of ‘2.2999999999999998e+0’ from ‘double’ to ‘int’ inside { } [-Wnarrowing]

c++11 提供了initializer_list<> 类来支撑上面的用法,我们也可以自己使用这个类,例如:

1
2
3
4
5
6
7
8
9
10
11
12
//tips
//标准库中的容器都有initializer_list为参赛的构造函数,故下面的
//initializer_list可以改为 deque,vector,list
auto print = [](std::initializer_list<int> vals)
{
for(auto p = vals.begin();p!=vals.end();++p)
{
std::cout<<*p<<std::endl;
}
};

print({1,2,3,4,5,6,7,13});

参数匹配规则:

  1. 如果有参数为initializer_list的函数,直接会调用对应参数为initializer_list的函数
  2. 如果没有参数为initializer_list的函数,则进行拆分initializer_list,去匹配有相同参数个数,相同参数类型的函数。
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
class P
{
public:
// fun1
P(int a, int b)
{
std::cout<< "P(int,int)"<<std::endl;
}
//fun2
P(std::initializer_list<int> initlist)
{
std::cout<<"P(initializer)"<<std::endl;
}
};
// fun1 与 fun2 同时存在的调用结果
P p(77, 5); // P(int,int)
P q{77, 5}; //P(initializer)
P r{77, 55, 26}; //P(initializer)
P s = {77, 5}; //P(initializer)

// 去掉构造函数fun2 只保留fun1
P p(77, 5); // P(int,int)
P q{77, 5}; //P(int,int)
P r{77, 55, 26}; //error: no matching function for call to ‘P::P(<brace-enclosed initializer list>)’
P s = {77, 5}; //P(int,int)

initializer_list 的实现原理

以下代码摘自 gcc 7.5.0

initializer_list的构造函数为 private故我们不能主动调用它,看注释可知这个构造函数是编译器可调用的。
故:当我们 使用大括号时 如{12,3,4,5},编译器会先生成一个数组,然后用数字的首地址,和size来构造initializer_list。

Tips

  1. const_iterator类型为const _E* 也就是元素的指针,它指向的是array的地址,故array 到initializer_list应该是浅拷贝。
  2. 标准库中用了大量的initializer_list,如容器的构造,插入,还有算法如:max({string(“abc”,string(“c”),string(“defg”)}),min({1,2,5,4,6,7,8})
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
27
28
29
30
31
32
33
34
35
36
//以下代码摘自 gcc 7.5.0 #include<initializer_list>
template<class _E>
class initializer_list
{
public:
typedef _E value_type;
typedef const _E& reference;
typedef const _E& const_reference;
typedef size_t size_type;
typedef const _E* iterator;
typedef const _E* const_iterator;

private:
iterator _M_array;
size_type _M_len;

// The compiler can call a private constructor.
constexpr initializer_list(const_iterator __a, size_type __l)
: _M_array(__a), _M_len(__l) { }

public:
constexpr initializer_list() noexcept
: _M_array(0), _M_len(0) { }

// Number of elements.
constexpr size_type
size() const noexcept { return _M_len; }

// First element.
constexpr const_iterator
begin() const noexcept { return _M_array; }

// One past the last element.
constexpr const_iterator
end() const noexcept { return begin() + size(); }
};

c++新特性之语法优化

模板表达式中的空格

1
2
vector<list<int> > //old 
vector<list<int>> //Ok since C++11

nullptr and std::nullptr_t

为了区分指针和整形增加了nullptr
使用nullptr 去代替0或者NULL

1
2
//<cstddef>
typedef decltype(nullptr) nullptr_t

类型std::nullptr_t 可以定义类型
具体使用看下面测试代码

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
27
28
29
30
31
#include<cstddef>
#include<iostream>
void f(int)
{
std::cout<<"int"<<std::endl;
}
void f(void *)
{
std::cout<<"void*"<<std::endl;
}
void f2(void *)
{
std::cout << "void*" << std::endl;
}
void f2(nullptr_t)
{
std::cout<<"nullptr_t"<<std::endl;
}
int main()
{
f(0); //int
f(12345);//int
f(nullptr);//void*
nullptr_t t;
if(t == nullptr) //true
{
std::cout<<"t == nullptr"<<std::endl;
}
f2(t); //nullptr_t
return 0;
}

auto

类型推倒 (一般使用在类型复杂,太长(迭代器),无法写的类型如lambda)

1
2
3
4
5
6
auto i = 42; //i 为int
auto l = [](int x)->bool{ //l为 匿名函数
return false;
}
vector<string> v;
auto v = v.begin();

作为函数返回值 decltype可以提取表达式的类型

1
2
3
4
5
6
auto i =42;
auto l = [](int x) -> decltype(x) {
std::cout<<x<<std::endl;
return x;
};
auto b = l(33);

Uniform Initialization(统一初始化)

含义:任何初始化动作都可以用共同的一种语法来初始化

1
2
3
int values[] {1,2,3};
std::vector<std::string> cities {"beijing","shanghaii","xian"};
std::complex<double> c{4.0,3.0};

编译器看到{t1,t2…tn}时便做出一个initializer_list,它关联一个array<T,n>。调用函数时(如ctor)这个array内的 元素可以被编译器分解并传给函数。但是如果函数参数是initializer_list时(容器都会有一个参数为它的构造函数)如上面的vector初始化,调用者无法知道参数的个数此时会把它们(array<T,n>)自动转换为一个initializer传进去。

1
std::vector<std::string> cities {"beijing","shanghaii","xian"};

上面的代码会形成一个 initializer_list,背后有一个 array<string,6>,调用vector ctors时编译器找到了一个接受initializer_list参数的构造函数。

1
std::complex<double> c{4.0,3.0};

上面的代码生成一个initializer_list,背后有一个array<double,2>。调用complex的构造函数时,这个array内的两个元素被分解传给构造函数。complex没有可以接受参数为 initializer_list的构造函数。

可变模板参数

侯捷老师的课程笔记

含义

类似下面的写法,在typename后面加了 … 表示一组东西,或者一包东西。
这一组东西,可以是任意类型。与c语言的的可变参数有点像。
使用variadic Templates 可以很方便的完成 recursive function call

示例

1
2
3
4
5
6
7
template<typename T,typename...Types>//任意类型
void print(const T& firstArg,const Types&...args)//任意个数
{
//sizeof...(args)
cout <<firstArg<<endl; //打印第一个参数
print(args...);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//结束递归使用
void print()
{
}

template<typename T,typename...Types>
void print(const T& firstArg,const Types&...args)
{
cout <<firstArg<<endl;
/*递归调用 一一分解参数,变为1+n的形式 直到把所有参数分解完,
然后调用上面的print()
*/
print(args...);
}
/*
example
1. print(123, others);
2. print(2.3 ,others);
3. print("abc",others);
4. print(bitset<16>(377),others(此时没有参数))
5. print();调用无形参的函数
*/
print(123,2.3,"abc",bitset<16>(377))

如果有下面的版本,那么可以编译通过吗?如果编译成功了会调用那一个?与上面的比较哪一个是特化?哪一个是范化?

1
2
3
4
5
template<typename...Types> 
void print(const Types&...args)
{
//todo do nothing
}

常见用法

万用哈希函数

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
27
28
29
30
31
32
33
34
35
36
37
38
#include<string>
#include<functional>
class Customer {
public:
std::string fname;
std::string lname;
std::string no;
}

class CustomerHash {
public:
std::size_t operator()(const Customer&c)const {
return hash_val(c.fname,c.lname.c.no);//调用函数1
}
// 1 首先进入这个函数
template<typename... Types>
inline std::size_t hash_val(const Types...args){
size_t seed = 0;
hash_val(seed,args...);//调用函数2 函数2是特化版本
return seed;
}
// 2
template<typename... Types>
inline void hash_val(std::szie_t& seed, const Types&...args){
hash_combine(seed,val);
hash_val(seed,args...);//递归调用函数2,直到剩余参数满足函数3,然后调用特化版本函数3结束递归调用
}
//3 auxiliary generic functions,用来结束递归。
template<typename T>
inline void hash_val(std::size_t& seed, const T& val){
hash_combine(seed,val);
}
//万用hash函数
template<typename T>
inline void hash_combine(std::size_t& seed, const T& val){
seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
}

tuple实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<typename... Values> class tuple;
template<> 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;}
inherited& tail {return *this;}
protected:
Head m_head;
}

c++11新特性

头文件

  • 在c++11(C++2.0)中c++标准库的头文件不带.h,例如#include<list>
  • 对于c头文件也不带.h,而是在前面加个c,例如 #include<cstdio>
  • 对于旧式的用法 #include<stdio.h>也支持

c++2.0 新加的头文件

1
2
3
4
5
6
7
#include<type_traits> //类型
#include<unordered_set>//无序set 哈希表
#include<forward_list> //单列表
#include<array> //数组
#include<tuple> //元组
#include<regex> //正则表达式
#include<thread> //线程

确认支持c++11

marco __cplusplus 可以查看编译器的c++版本

1
2
3
4
5
6
7
8
9
10
#include<iostream>
using namespace std;

int main()
{
#ifdef __cplusplus
cout<<__cplusplus<<endl;
#endif
return 0;
}

学习网址