简介
介绍
配置
- g++在版本中配置:
-std=c++11
- qt中配置:
CONFIG+=c++11
- cmake-g++:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
类型推导
auto
- 在C中,auto修饰局部变量,局部变量也叫auto变量,自动变量
- C++11, auto根据用户初始化内容自动推导类型
auto b=1; vector<int> a; a.push_back(1);a.push_back(2);a.push_back(3); for (auto i=a.begin();i!=a.end();i++){}
|
注意点
- 定义变量时,必须初始化:
auto a;a = 10;
- vs2013不支持,函数形参是auto变量, qt可以:
void func(auto a)
- auto变量不能作为自定义类型的成员变量:
struct Test{int a;auto b = 10;//不允许xxxx};
- 不能是auto数组:
auto b[3] = { 1, 2, 3 };
- 模板实例化类型不能是auto类型:
vector<auto> b = { 1 };
decltype
- decltype实际上有点像auto的反函数, auto可以让你声明一个变量,而decltype则可以从一个变量或表达式中得到其类型
#include <typeinfo> //使用typeid #include <iostream> #include <vector> using namespace std; int main() { int i; decltype(i) j = 0; cout << typeid(j).name() << endl; float a; double b; decltype(a + b) c; cout << typeid(c).name() << endl; vector<int> vec; typedef decltype(vec.begin()) vectype; vectype k; for (k = vec.begin(); k < vec.end(); k++) { } enum {Ok, Error, Warning}flag; decltype(flag) tmp = Ok; return 0; }
|
追踪返回类型
- 返回类型后置:在函数名和参数列表后面指定返回类型。
int func(int, int); auto func2(int, int) -> int; auto func3(int, double) -> decltype(a+b); template<typename T1, typename T2> auto sum(const T1 & t1, const T2 & t2) -> decltype(t1 + t2){ return t1 + t2; } template <typename T1, typename T2> auto mul(const T1 & t1, const T2 & t2) -> decltype(t1 * t2){ return t1 * t2; } int main(){ auto a = 3;auto b = 4L;auto pi = 3.14; auto c = mul( sum(a, b), pi ); cout << c << endl; return 0; }
|
易用性的改进
初始化
类内成员初始化
class A { public: A(int i) : a(i) { } int a; }; class B { public: int data{ 1 }; int data2 = 1; A tmp{ 10 }; string name{ "mike" }; };
|
列表初始化
- C++11引入了一个新的初始化方式,称为初始化列表(List Initialize),具体的初始化方式如下:
int a[]{1, 3, 5}; int i = {1}; int j{3};
|
struct Person { std::string name; int age; }; int main() { Person p = {"Frank", 25}; std::cout << p.name << " : " << p.age << std::endl; }
|
- 其他一些不方便初始化的地方使用,比如std的初始化,如果不使用这种方式,只能用构造函数来初始化,难以达到效果:
std::vector<int> ivec1(3, 5); std::vector<int> ivec2 = {5, 5, 5}; std::vector<int> ivec3 = {1,2,3,4,5};
|
防止类型收窄
- 类型收窄指的是导致数据内容发生变化或者精度丢失的隐式类型转换。使用列表初始化在编译过程中会对数据进行校验,发生精度丢失则报错,可以防止类型收窄。
int main(void) { const int x = 1024; const int y = 10; char a = x; char* b = new char(1024); char c = { x }; char d = { y }; unsigned char e{ -1 }; float f{ 7 }; int g{ 2.0f }; float * h = new float{ 1e48 }; float i = 1.2l; return 0; }
|
基于范围的for循环
- 在C++中for循环可以使用基于范围的for循环,示例代码如下:
int main() { int a[5] = { 1, 2, 3, 4, 5 }; for (int & e: a){ e *= 2; } for (int & e: a){ cout << e << ", "; } cout << endl; return 0; }
|
- 使用基于范围的for循环,其for循环迭代的范围必须是可确定的:
int func(int a[]){ for(auto e: a) { cout << e; } } int main(){ int a[] = {1, 2, 3, 4, 5}; func(a); return 0; }
|
静态断言
- C/C++提供了调试工具assert,这是一个宏,用于在运行阶段对断言进行检查,如果条件为真,执行程序,否则调用abort()。
#include<cassert> int main() { bool flag = false; assert(flag == true); cout << "Hello World!" << endl;
return 0; }
|
- C++ 11新增了关键字static_assert,可用于在编译阶段对断言进行测试。
静态断言的好处
-
更早的报告错误,我们知道构建是早于运行的,更早的错误报告意味着开发成本的降低
-
减少运行时开销,静态断言是编译期检测的,减少了运行时开销
-
语法:static_assert(常量表达式,提示字符串)
注意:只能是常量表达式,不能是变量
int main() { static_assert( sizeof(void *)== 4, "64-bit code generation is not supported."); cout << "Hello World!" << endl; return 0; }
|
noexcept修饰符(vs2013不支持)
void func3() throw(int, char) { throw 0; } void BlockThrow() throw() { throw 1; }
void BlockThrowPro() noexcept { throw 2; }
|
nullptr
- nullptr是为了解决原来C++中NULL的二义性问题而引进的一种新的类型,因为NULL实际上代表的是0。
void func(int a) { cout << __LINE__ << " a = " << a <<endl; } void func(int *p) { cout << __LINE__ << " p = " << p <<endl; } int main() { int *p1 = nullptr; int *p2 = NULL; if(p1 == p2) { cout << "equal\n"; } func(0); func(nullptr); return 0; }
|
强类型枚举
- C++ 11引入了一种新的枚举类型,即“枚举类”,又称“强类型枚举”。声明请类型枚举非常简单,只需要在enum后加上使用class或struct。如:
enum Old{Yes, No}; enum class New{Yes, No}; enum struct New2{Yes, No};
|
- “传统”的C++枚举类型有一些缺点:它会在一个代码区间中抛出枚举类型成员(如果在相同的代码域中的两个枚举类型具有相同名字的枚举成员,这会导致命名冲突),它们会被隐式转换为整型,并且不可以指定枚举的底层数据类型。
int main() { enum Status{Ok, Error}; return 0; }
|
int main() { enum class Status {Ok, Error}; enum struct Status2{Ok, Error}; Status flag3 = Status::Ok; enum class C : char { C1 = 1, C2 = 2}; enum class D : unsigned int { D1 = 1, D2 = 2, Dbig = 0xFFFFFFF0U }; cout << sizeof(C::C1) << endl; cout << sizeof(D::D1) << endl; cout << sizeof(D::Dbig) << endl; return 0; }
|
常量表达式(vs2013 不支持)
- 常量表达式主要是允许一些计算发生在编译时,即发生在代码编译而不是运行的时候。
- 这是很大的优化:假如有些事情可以在编译时做,它将只做一次,而不是每次程序运行时都计算。
- 使用constexpr,你可以创建一个编译时的函数:
constexpr int GetConst() { return 3; } int main() { int arr[ GetConst() ] = {0}; enum { e1 = GetConst(), e2 }; constexpr int num = GetConst(); return 0; }
|
constexpr函数的限制
- 函数中只能有一个return语句(有极少特例)
- 函数必须返回值(不能是void函数)
- 在使用前必须已有定义
- return返回语句表达式中不能使用非常量表达式的函数、全局数据,且必须是一个常量表达式
constexpr int data() { constexpr int i = 1; return i; } constexpr int data2() { static_assert(1, "fail"); return 100; } int a = 3; constexpr int data3() { return a; } int main() { constexpr int func(); constexpr int c = func(); return 0; } constexpr int func() { return 1; }
|
struct Date { constexpr Date(int y, int m, int d): year(y), month(m), day(d) {} constexpr int GetYear() { return year; } constexpr int GetMonth() { return month; } constexpr int GetDay() { return day; } private: int year; int month; int day; }; int main() { constexpr Date PRCfound {1949, 10, 1}; constexpr int foundmonth = PRCfound.GetMonth(); cout << foundmonth << endl; return 0; }
|
用户定义字面量(vs2013 不支持)
- 用户自定义字面值,或者叫“自定义后缀”更直观些,主要作用是简化代码的读写。
long double operator"" _mm(long double x) { return x / 1000; } long double operator"" _m(long double x) { return x; } long double operator"" _km(long double x) { return x * 1000; } int main() { cout << operate"" _mm(1) << endl; cout << 1.0_mm << endl; cout << 1.0_m << endl; cout << 1.0_km << endl; return 0; }
|
- 根据 C++ 11 标准,只有下面参数列表才是合法的:
char const * unsigned long long long double char const *, size_t wchar_t const *, size_t char16_t const *, size_t char32_t const *, size_t
|
- 最后四个对于字符串相当有用,因为第二个参数会自动推断为字符串的长度。例如:
size_t operator"" _len(char const * str, size_t size) { return size; } int main() { cout << "mike"_len <<endl; return 0; }
|
- 对于参数char const *,应该被称为原始字面量 raw literal 操作符。例如:
char const * operator"" _r(char const* str) { return str; } int main() { cout << 250_r <<endl; return 0; }
|
原生字符串字面值
- 原生字符串字面值(raw string literal)使用户书写的字符串“所见即所得”。C++11中原生字符串的声明相当简单,只需在字符串前加入前缀,即字母R,并在引号中使用括号左右标识,就可以声明该字符串字面量为原生字符串了。
int main() { cout << R"(hello,\n world)" << endl; return 0; }
|
类的改进
继承构造(vs2013 不支持)
- C++ 11允许派生类继承基类的构造函数(默认构造函数、复制构造函数、移动构造函数除外)。
class A { public: A(int i) { cout << "i = " << i << endl; } A(double d, int i) {} A(float f, int i, const char* c) {} }; class B : public A { public: using A::A; virtual void ExtraInterface(){} };
|
注意
- 继承的构造函数只能初始化基类中的成员变量,不能初始化派生类的成员变量
- 如果基类的构造函数被声明为私有,或者派生类是从基类中虚继承,那么不能继承构造函数
- 一旦使用继承构造函数,编译器不会再为派生类生成默认构造函数
委托构造
- 和继承构造函数类似,委托构造函数也是C11中对C的构造函数的一项改进,其目的也是为了减少程序员书写构造函数的时间。
- 如果一个类包含多个构造函数,C++ 11允许在一个构造函数中的定义中使用另一个构造函数,但这必须通过初始化列表进行操作,如下:
class Info { public: Info() : Info(1) { } Info(int i) : Info(i, 'a') { } Info(char e): Info(1, e) { }
private: Info(int i, char e): type(i), name(e) { } int type; char name; };
|
继承控制:final和override
- C++11之前,一直没有继承控制关键字,禁用一个类的进一步衍生比较麻烦。
- C++ 11添加了两个继承控制关键字:final和override。
- final阻止类的进一步派生和虚函数的进一步重写
- override确保在派生类中声明的函数跟基类的虚函数有相同的签名,要求一模一样
class B1 final {};
class B { public:
virtual void f() const { cout << __func__ << std::endl; } virtual void fun() { cout << __func__ << std::endl; } }; class D : public B { public: virtual void f(int) { cout << "hiding: " <<__func__ << std::endl; }
virtual void fun() override final { cout << __func__ << std::endl; } }; class D2 : public D { public: virtual void f() const { cout << __func__ << std::endl; }
};
|
类默认函数的控制:“default” 和 "delete"函数
"=default"函数
- C++ 的类有四类特殊成员函数,它们分别是:默认构造函数、析构函数、拷贝构造函数以及拷贝赋值运算符。这些类的特殊成员函数负责创建、初始化、销毁,或者拷贝类的对象。如果程序员没有显式地为一个类定义某个特殊成员函数,而又需要用到该特殊成员函数时,则编译器会隐式的为这个类生成一个默认的特殊成员函数。
- 但是,如果程序员为类显式的自定义了非默认构造函数,编译器将不再会为它隐式地生成默认无参构造函数。
class X { public: X(){}
X(int i) { a = i; } private: int a; }; X obj;
|
-
原本期望编译器自动生成的默认构造函数却需要程序员手动编写,即程序员的工作量加大了。此外,手动编写的默认构造函数的代码执行效率比编译器自动生成的默认构造函数低。
-
类的其它几类特殊成员函数也和默认构造函数一样,当存在用户自定义的特殊成员函数时,编译器将不会隐式的自动生成默认特殊成员函数,而需要程序员手动编写,加大了程序员的工作量。类似的,手动编写的特殊成员函数的代码执行效率比编译器自动生成的特殊成员函数低。
-
C++11 标准引入了一个新特性:"=default"函数。程序员只需在函数声明后加上“=default;”,就可将该函数声明为 "=default"函数,编译器将为显式声明的 "=default"函数自动生成函数体。
class X { public: X()= default; X(int i) { a = i; } private: int a; };
X obj;
|
- "=default"函数特性仅适用于类的特殊成员函数,且该特殊成员函数没有默认参数。例如:
class X { public: int f() = default; X(int, int) = default; X(int = 1) = default; };
|
- "=default"函数既可以在类体里(inline)定义,也可以在类体外(out-of-line)定义。例如:
class X { public: X() = default; X(const X&); X& operator = (const X&); ~X() = default; };
X::X(const X&) = default; X& X::operator= (const X&) = default;
|
"=delete"函数
- 为了能够让程序员显式的禁用某个函数,C++11 标准引入了一个新特性:"=delete"函数。程序员只需在函数声明后上“=delete;”,就可将该函数禁用。
class X { public: X(); X(const X&) = delete; X& operator = (const X &) = delete; };
int main() { X obj1; X obj2=obj1;
X obj3; obj3=obj1;
return 0; }
|
- "=delete"函数特性还可用于禁用类的某些转换构造函数,从而避免不期望的类型转换:
class X { public: X(double) {
}
X(int) = delete; };
int main() { X obj1(1.2); X obj2(2);
return 0; }
|
- "=delete"函数特性还可以用来禁用某些用户自定义的类的 new 操作符,从而避免在自由存储区创建类的对象:
class X { public: void *operator new(size_t) = delete; void *operator new[](size_t) = delete; };
int main() { X *pa = new X; X *pb = new X[10];
return 0; }
|
模板的改进
右尖括号>改进
- 在C++98/03的泛型编程中,模板实例化有一个很繁琐的地方,就是连续两个右尖括号(>>)会被编译解释成右移操作符,而不是模板参数表的形式,需要一个空格进行分割,以避免发生编译时的错误。
template <int i> class X{}; template <class T> class Y{}; int main() { Y<X<1> > x1; Y<X<2>> x2; return 0; };
|
- 在实例化模板时会出现连续两个右尖括号,同样static_cast、dynamic_cast、reinterpret_cast、const_cast表达式转换时也会遇到相同的情况。C98标准是让程序员在>>之间填上一个空格,在C11中,这种限制被取消了。在C++11标准中,要求编译器对模板的右尖括号做单独处理,使编译器能够正确判断出">>"是一个右移操作符还是模板参数表的结束标记。
模板的别名
#include <iostream> #include <type_traits> //std::is_same using namespace std; using uint = unsigned int; typedef unsigned int UINT; using sint = int; int main() { cout << is_same<uint, UINT>::value << endl; return 0; }
|
函数模板的默认模板参数
- C++11之前,类模板是支持默认的模板参数,却不支持函数模板的默认模板参数:
void DefParm(int m = 3) {}
template <typename T = int> class DefClass {};
template <typename T = int> void DefTempParm() {}
|
- 类模板的默认模板参数必须从右往左定义,数模板的默认模板参数则没这个限定:
template<class T1, class T2 = int> class DefClass1; template<class T1 = int, class T2> class DefClass2;
template<class T, int i = 0> class DefClass3; template<int i = 0, class T> class DefClass4;
template<class T1 = int, class T2> void DefFunc1(T1 a, T2 b); template<int i = 0, class T> void DefFunc2(T a);
|
可变参数的模板
template<class ... T> void func(T ... args)//T叫模板参数包,args叫函数参数包 {
}
func(); func(1); func(2, 1.0);
|
- 省略号“…”的作用有两个:
- 声明一个参数包,这个参数包中可以包含0到任意个模板参数
- 在模板定义的右边,可以将参数包展开成一个一个独立的参数
可变参数模板函数
可变参数模板函数的定义
template<class ... T> void func(T ... args) { cout << "num = " << sizeof...(args) << endl; } int main() { func(); func(1); func(2, 1.0);
return 0; }
|
参数包的展开
递归方式展开
- 通过递归函数展开参数包,需要提供一个参数包展开的函数和一个递归终止函数。
void debug() { cout << "empty\n"; }
template <class T, class ... Args> void debug(T first, Args ... last) { cout << "parameter " << first << endl; debug(last...); }
int main() { debug(1, 2, 3, 4);
return 0; }
|
debug(1, 2, 3, 4); debug(2, 3, 4); debug(3, 4); debug(4); debug();
|
非递归方式展开
template <class T> void print(T arg) { cout << arg << endl; }
template <class ... Args> void expand(Args ... args) { int a[] = { (print(args), 0)... }; }
int main() { expand(1, 2, 3, 4);
return 0; }
|
-
expand函数的逗号表达式:(print(args), 0), 也是按照这个执行顺序,先执行print(args),再得到逗号表达式的结果0。
-
同时,通过初始化列表来初始化一个变长数组,{ (print(args), 0)… }将会展开成( (print(args1), 0), (print(args2), 0), (print(args3), 0), etc…), 最终会创建一个元素只都为0的数组int a[sizeof…(args)]。
可变参数模板类
继承方式展开参数包
- 可变参数模板类的展开一般需要定义2 ~ 3个类,包含类声明和特化的模板类:
template<typename... A> class BMW{};
template<typename Head, typename... Tail> class BMW<Head, Tail...> : public BMW<Tail...> { public: BMW() { printf("type: %s\n", typeid(Head).name()); }
Head head; };
template<> class BMW<>{};
int main() { BMW<int, char, float> car;
return 0; }
|
模板递归和特化方式展开参数包
template <long... nums> struct Multiply;
template <long first, long... last> struct Multiply<first, last...> // 变长模板类 { static const long val = first * Multiply<last...>::val; };
template<> struct Multiply<> // 边界条件 { static const long val = 1; };
int main() { cout << Multiply<2, 3, 4, 5>::val << endl;
return 0; }
|
右值引用
左值引用、右值引用
左值、右值
- 在C语言中,我们常常会提起左值(lvalue)、右值(rvalue)这样的称呼。一个最为典型的判别方法就是,在赋值表达式中,出现在等号左边的就是“左值”,而在等号右边的,则称为“右值”。如:
int b = 1; int c = 2; int a = a + b;
|
-
在这个赋值表达式中,a就是一个左值,而b + c则是一个右值。
-
不过C++中还有一个被广泛认同的说法,那就是可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值。那么这个加法赋值表达式中,&a是允许的操作,但&(b + c)这样的操作则不会通过编译。因此a是一个左值,(b + c)是一个右值。
-
相对于左值,右值表示字面常量、表达式、函数的非引用返回值等。
左值引用、右值引用
-
左值引用是对一个左值进行引用的类型,右值引用则是对一个右值进行引用的类型。
-
左值引用和右值引用都是属于引用类型。无论是声明一个左值引用还是右值引用,都必须立即进行初始化。而其原因可以理解为是引用类型本身自己并不拥有所绑定对象的内存,只是该对象的一个别名。
-
左值引用是具名变量值的别名,而右值引用则是不具名(匿名)变量的别名。
-
左值引用:
int &a = 2; int b = 2; const int &c = b; const int d = 2; const int &e = c; const int &b = 2;
|
int && r1 = 22; int x = 5; int y = 8; int && r2 = x + y; T && a = ReturnRvalue();
|
void process_value(int & i) { cout << "LValue processed: " << i << endl; }
void process_value(int && i) { cout << "RValue processed: " << i << endl; }
int main() { int a = 0; process_value(a); process_value(1);
return 0; }
|
移动语义
为什么需要移动语义
-
右值引用是用来支持转移语义的。转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高 C++ 应用程序的性能。临时对象的维护 ( 创建和销毁 ) 对性能有严重影响。
-
转移语义是和拷贝语义相对的,可以类比文件的剪切与拷贝,当我们将文件从一个目录拷贝到另一个目录时,速度比剪切慢很多。
-
通过转移语义,临时对象中的资源能够转移其它的对象里。
移动语义定义
-
在现有的 C++ 机制中,我们可以定义拷贝构造函数和赋值函数。要实现转移语义,需要定义转移构造函数,还可以定义转移赋值操作符。对于右值的拷贝和赋值会调用转移构造函数和转移赋值操作符。
-
如果转移构造函数和转移拷贝操作符没有定义,那么就遵循现有的机制,拷贝构造函数和赋值操作符会被调用。
-
普通的函数和操作符也可以利用右值引用操作符实现转移语义。
转移构造函数
class MyString { public: MyString(const char *tmp = "abc") { len = strlen(tmp); str = new char[len+1]; strcpy(str, tmp);
cout << "普通构造函数 str = " << str << endl; }
MyString(const MyString &tmp) { len = tmp.len; str = new char[len + 1]; strcpy(str, tmp.str);
cout << "拷贝构造函数 tmp.str = " << tmp.str << endl; }
MyString(MyString && t) { str = t.str; len = t.len;
t.str = NULL; cout << "移动构造函数" << endl; }
MyString &operator= (const MyString &tmp) { if(&tmp == this) { return *this; }
len = 0; delete []str;
len = tmp.len; str = new char[len + 1]; strcpy(str, tmp.str);
cout << "赋值运算符重载函数 tmp.str = " << tmp.str << endl;
return *this;
}
~MyString() { cout << "析构函数: "; if(str != NULL) { cout << "已操作delete, str = " << str; delete []str; str = NULL; len = 0;
} cout << endl; }
private: char *str = NULL; int len = 0; };
MyString func() { MyString obj("mike");
return obj; }
int main() { MyString &&tmp = func();
return 0; }
|
转移赋值函数
class MyString { public: MyString(const char *tmp = "abc") { len = strlen(tmp); str = new char[len+1]; strcpy(str, tmp);
cout << "普通构造函数 str = " << str << endl; }
MyString(const MyString &tmp) { len = tmp.len; str = new char[len + 1]; strcpy(str, tmp.str);
cout << "拷贝构造函数 tmp.str = " << tmp.str << endl; }
MyString(MyString && t) { str = t.str; len = t.len;
t.str = NULL; cout << "移动构造函数" << endl; }
MyString &operator= (const MyString &tmp) { if(&tmp == this) { return *this; }
len = 0; delete []str;
len = tmp.len; str = new char[len + 1]; strcpy(str, tmp.str);
cout << "赋值运算符重载函数 tmp.str = " << tmp.str << endl;
return *this;
}
MyString &operator=(MyString &&tmp) { if(&tmp == this) { return *this; }
len = 0; delete []str;
len = tmp.len; str = tmp.str; tmp.str = NULL;
cout << "移动赋值函数\n";
return *this; }
~MyString() { cout << "析构函数: "; if(str != NULL) { cout << "已操作delete, str = " << str; delete []str; str = NULL; len = 0;
} cout << endl; }
private: char *str = NULL; int len = 0; };
MyString func() { MyString obj("mike");
return obj; }
int main() { MyString tmp("abc"); tmp = func();
return 0; }
|
标准库函数 std::move
- 既然编译器只对右值引用才能调用转移构造函数和转移赋值函数,而所有命名对象都只能是左值引用,如果已知一个命名对象不再被使用而想对它调用转移构造函数和转移赋值函数,也就是把一个左值引用当做右值引用来使用,怎么做呢?标准库提供了函数 std::move,这个函数以非常简单的方式将左值引用转换为右值引用。
int a; int &&r1 = a; int &&r2 = std::move(a);
|
完美转发 std::forward
#include <iostream> using namespace std;
template <typename T> void process_value(T & val) { cout << "T &" << endl; }
template <typename T> void process_value(const T & val) { cout << "const T &" << endl; }
template <typename T> void forward_value(const T& val) { process_value(val); }
template <typename T> void forward_value(T& val) { process_value(val); }
int main() { int a = 0; const int &b = 1;
forward_value(a); forward_value(b); forward_value(2);
return 0; }
|
typedef const int T; typedef T & TR; TR &v = 1;
|
| TR的类型定义 |
声明v的类型 |
v的实际类型 |
| T & |
TR |
T & |
| T & |
TR & |
T & |
| T & |
TR && |
T & |
| T && |
TR |
T && |
| T && |
TR & |
T & |
| T && |
TR && |
T && |
#include <iostream> using namespace std; template <typename T> void process_value(T & val){ cout << "T &" << endl; } template <typename T> void process_value(T && val){ cout << "T &&" << endl; } template <typename T> void process_value(const T & val){ cout << "const T &" << endl; } template <typename T> void process_value(const T && val){ cout << "const T &&" << endl; }
template <typename T> void forward_value(T && val) process_value( std::forward<T>(val) ); } int main(){ int a = 0; const int &b = 1; forward_value(a); forward_value(b); forward_value(2); forward_value( std::move(b) ); return 0; }
|
智能指针
- C++11中有unique_ptr、shared_ptr与weak_ptr等智能指针(smart pointer),定义在中。可以对动态资源进行管理,保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。
unique_ptr
-
unique_ptr持有对对象的独有权,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。
-
unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。
-
离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。
#include <iostream> #include <memory> using namespace std; int main(){ unique_ptr<int> up1(new int(11)); cout << *up1 << endl; unique_ptr<int> up3 = move(up1); cout << *up3 << endl; up3.reset(); up1.reset(); unique_ptr<int> up4(new int(22)); up4.reset(new int(44)); cout << *up4 << endl; up4 = nullptr; unique_ptr<int> up5(new int(55)); int *p = up5.release(); cout << *p << endl; delete p; return 0; }
|
shared_ptr
- shared_ptr允许多个该智能指针共享第“拥有”同一堆分配对象的内存,这通过引用计数(reference counting)实现,会记录有多少个shared_ptr共同指向一个对象,一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。
int main() { shared_ptr<int> sp1(new int(22)); shared_ptr<int> sp2 = sp1; cout << "count: " << sp2.use_count() << endl; cout << *sp1 << endl; cout << *sp2 << endl; sp1.reset(); cout << "count: " << sp2.use_count() << endl; cout << *sp2 << endl; return 0; }
|
weak_ptr
-
weak_ptr是为配合shared_ptr而引入的一种智能指针来协助shared_ptr工作,它可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起引用计数的增加或减少。没有重载 * 和 -> 但可以使用lock获得一个可用的shared_ptr对象
-
weak_ptr的使用更为复杂一点,它可以指向shared_ptr指针指向的对象内存,却并不拥有该内存,而使用weak_ptr成员lock,则可返回其指向内存的一个share_ptr对象,且在所指对象内存已经无效时,返回指针空值nullptr。
void check(weak_ptr<int> &wp) { shared_ptr<int> sp = wp.lock(); if (sp != nullptr) { cout << "still " << *sp << endl; } else { cout << "pointer is invalid" << endl; } } int main() { shared_ptr<int> sp1(new int(22)); shared_ptr<int> sp2 = sp1; weak_ptr<int> wp = sp1; cout << "count: " << wp.use_count() << endl; cout << *sp1 << endl; cout << *sp2 << endl; check(wp); sp1.reset(); cout << "count: " << wp.use_count() << endl; cout << *sp2 << endl; check(wp); sp2.reset(); cout << "count: " << wp.use_count() << endl; check(wp); return 0; }
|
闭包的实现
什么是闭包
-
闭包有很多种定义,一种说法是,闭包是带有上下文的函数。说白了,就是有状态的函数。更直接一些,不就是个类吗?换了个名字而已。
-
一个函数,带上了一个状态,就变成了闭包了。那什么叫 “带上状态” 呢? 意思是这个闭包有属于自己的变量,这些个变量的值是创建闭包的时候设置的,并在调用闭包的时候,可以访问这些变量。
-
函数是代码,状态是一组变量,将代码和一组变量捆绑 (bind) ,就形成了闭包。
-
闭包的状态捆绑,必须发生在运行时。
闭包的实现
仿函数:重载 operator()
class MyFunctor { public: MyFunctor(int tmp) : round(tmp) {} int operator()(int tmp) { return tmp + round; } private: int round; }; int main() { int round = 2; MyFunctor f(round); cout << "result = " << f(1) << endl; return 0; }
|
std::bind绑定器
std::function
#include <iostream> #include <functional> //std::cout using namespace std; void func(void) { cout << __func__ << endl; } class Foo { public: static int foo_func(int a) { cout << __func__ << "(" << a << ") ->: "; return a; } }; class Bar { public: int operator()(int a) { cout << __func__ << "(" << a << ") ->: "; return a; } }; int main() { function< void(void) > f1 = func; f1(); function< int(int) > f2 = Foo::foo_func; cout << f2(111) << endl; Bar obj; f2 = obj; cout << f2(222) << endl;
return 0; }
|
- std::function对象最大的用处就是在实现函数回调,使用者需要注意,它不能被用来检查相等或者不相等,但是可以与NULL或者nullptr进行比较。
std::bind
-
std::bind是这样一种机制,它可以预先把指定可调用实体的某些参数绑定到已有的变量,产生一个新的可调用实体,这种机制在回调函数的使用过程中也颇为有用。
-
C++98中,有两个函数bind1st和bind2nd,它们分别可以用来绑定functor的第一个和第二个参数,它们都是只可以绑定一个参数,各种限制,使得bind1st和bind2nd的可用性大大降低。
-
在C++11中,提供了std::bind,它绑定的参数的个数不受限制,绑定的具体哪些参数也不受限制,由用户指定,这个bind才是真正意义上的绑定。
-
std::bind的基本语法:
#include <iostream> #include <functional> //std::bind using namespace std; void func(int x, int y) { cout << x << " " << y << endl; } int main() { bind(func, 1, 2)(); bind(func, std::placeholders::_1, 2)(1); using namespace std::placeholders; bind(func, 2, _1)(1); bind(func, 2, _2)(1, 2); bind(func, _1, _2)(1, 2); bind(func,_2, _1)(1, 2); return 0; }
|
- std::placeholders::_1是一个占位符,代表这个位置将在函数调用时,被传入的第一个参数所替代。
std::bind和std::function配合使用
#include <iostream> #include <functional> //std::cout using namespace std; using namespace std::placeholders;
class Test { public: int i = 0; void func(int x, int y) { cout << x << " " << y << endl; } }; int main() { Test obj; function<void(int, int)> f1 = bind(&Test::func, &obj, _1, _2); f1(1, 2); function< int &()> f2 = bind(&Test::i, &obj); f2() = 123; cout << obj.i << endl; return 0; }
|
- 通过std::bind和std::function配合使用,所有的可调用对象均有了统一的操作方法。
lambda表达式
lambda基础使用
lambda表达式的基本构成
- 函数对象参数
- [],标识一个lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义lambda为止时lambda所在作用范围内可见的局部变量(包括lambda所在类的this)。函数对象参数有以下形式:
- 空。没有使用任何函数对象参数。
- =。函数体内可以使用lambda所在作用范围内所有可见的局部变量(包括lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
- &。函数体内可以使用lambda所在作用范围内所有可见的局部变量(包括lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
- this。函数体内可以使用lambda所在类中的成员变量。
- a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
- &a。将a按引用进行传递。
- a, &b。将a按值进行传递,b按引用进行传递。
- =,&a, &b。除a和b按引用进行传递外,其他参数都按值进行传递。
- &, a, b。除a和b按值进行传递外,其他参数都按引用进行传递。
- 操作符重载函数参数
- 标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。
- 可修改标示符
- mutable声明,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。
- 错误抛出标示符
- exception声明,这部分也可以省略。exception声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用throw(int)
- 函数返回值
- ->返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。
- 是函数体
- {},标识函数的实现,这部分不能省略,但函数体可以为空。
class Test { public: int i = 0; void func(int x, int y) { auto x1 = []{ return i; }; auto x2 = [=]{ return i+x+y; }; auto x3 = [=]{ return i+x+y; }; auto x4 = [this]{ return i; }; auto x5 = [this]{ return i+x+y; }; auto x6 = [this, x, y]{ return i+x+y; }; auto x9 = [this]{ return i++; }; } };
int main() { int a = 0, b = 1; auto f1 = []{ return a; }; auto f2 = [=]{ return a; }; auto f3 = [=]{ return a++; }; auto f4 = [=]() mutable { return a++; }; auto f5 = [&]{ return a++; }; auto f6 = [a]{ return a+b; }; auto f9 = [a,&b]{ return a+(b++); }; auto f8 = [=,&b]{ return a+(b++); }; return 0; }
|
int main() { int j = 12; auto by_val_lambda = [=] { return j + 1;}; auto by_ref_lambda = [&] { return j + 1;}; cout << "by_val_lambda: " << by_val_lambda() << endl; cout << "by_ref_lambda: " << by_ref_lambda() << endl; j++; cout << "by_val_lambda: " << by_val_lambda() << endl; cout << "by_ref_lambda: " << by_ref_lambda() << endl;
return 0; }
|
- 第3次调用结果还是13,原因是由于by_val_lambda中,j被视为了一个常量,一旦初始化后不会再改变。
lambda与仿函数
class MyFunctor { public: MyFunctor(int tmp) : round(tmp) {} int operator()(int tmp) { return tmp + round; } private: int round; }; int main() { int round = 2; MyFunctor f1(round); cout << "result1 = " << f1(1) << endl; auto f2 = [=](int tmp) -> int { return tmp + round; } ; cout << "result2 = " << f2(1) << endl; return 0; }
|
-
通过上面的例子,我们看到,仿函数以round初始化类,而lambda函数也捕获了round变量,其它的,如果在参数传递上,两者保持一致。
-
除去在语法层面上的不同,lambda和仿函数有着相同的内涵——都可以捕获一些变量作为初始化状态,并接受参数进行运行。
-
而事实上,仿函数是编译器实现lambda的一种方式,通过编译器都是把lambda表达式转化为一个仿函数对象。因此,在C++11中,lambda可以视为仿函数的一种等价形式。
lambda类型
int main() { function<int(int)> f1 = [](int a) { return a; }; function<int()> f2 = bind([](int a){ return a; }, 123); cout << "f1 = " << f1(123) << endl; cout << "f2 = " << f2() << endl; auto f3 = [](int x, int y)->int{ return x + y; }; typedef int (*PF1)(int x, int y); typedef int (*PF2)(int x); PF1 p1; p1 = f3; cout << "p1 = " << p1(3, 4) << endl; PF2 p2; p2 = f3; decltype(f3) p3 = f3; decltype(f3) p4 = p1; return 0; }
|
lambda优势
#include <vector> #include <algorithm> //std::for_each #include <iostream> using namespace std; vector<int> nums; vector<int> largeNums; class LNums { public: LNums(int u): ubound(u){} void operator () (int i) const { if (i > ubound) { largeNums.push_back(i); } } private: int ubound; }; int main() { for(auto i = 0; i < 10; ++i) { nums.push_back(i); } int ubound = 5; for (auto itr = nums.begin(); itr != nums.end(); ++itr) { if (*itr > ubound) { largeNums.push_back(*itr); } } for_each(nums.begin(), nums.end(), LNums(ubound)); for_each(nums.begin(), nums.end(), [=](int i) { if (i > ubound) { largeNums.push_back(i); } } ); for_each(largeNums.begin(), largeNums.end(), [=](int i) { cout << i << ", "; } ); cout << endl; return 0; }
|
- lambda表达式的价值在于,就地封装短小的功能闭包,可以及其方便地表达出我们希望执行的具体操作,并让上下文结合更加紧密。
线程
线程的使用
线程的创建
- 用std::thread创建线程非常简单,只需要提供线程函数或函数对象即可,并且可以同时指定线程函数的参数。
#include <thread> #include <iostream> using namespace std; void func1() { while(1) { cout << __func__ << endl; } } void func2() { while(1) { cout << __func__ << endl; } } int main() { thread t1(func1); thread t2(func2); while(1) { cout << __func__ << endl; } return 0; }
|
void func(int a, char ch, const char *str) { std::cout << "a = " << a << "\n"; std::cout << "ch = " << ch << "\n"; std::cout << "str = " << str << "\n"; } int main() { std::thread t(func, 1, 'a', "mike"); while(1); return 0; }
|
回收线程资源
- std::thread::join等待线程结束(此函数会阻塞),并回收线程资源,如果线程函数有返回值,返回值将被忽略。
#include <iostream> // std::cout #include <thread> // std::thread, std::this_thread::sleep_for #include <chrono> // std::chrono::seconds using namespace std; void pause_thread(int n) { this_thread::sleep_for(chrono::seconds(n)); cout << "pause of " << n << " seconds ended\n"; } int main() { cout << "Spawning 3 threads...\n"; thread t1(pause_thread, 1); thread t2(pause_thread, 2); thread t3(pause_thread, 3); cout << "Done spawning threads. Now waiting for them to join:\n"; t1.join(); t2.join(); t3.join(); cout << "All threads joined!\n"; return 0; }
|
- 如果不希望线程被阻塞执行,可以调用线程的std::thread::detach,将线程和线程对象分离,让线程作为后台线程去执行。但需要注意的是,detach之后就无法在和线程发生联系了,比如detach之后就不能再通过join来等待执行完,线程何时执行完我们也无法控制。
void pause_thread(int n) { this_thread::sleep_for (chrono::seconds(n)); cout << "pause of " << n << " seconds ended\n"; }
int main() { cout << "Spawning and detaching 3 threads...\n"; thread(pause_thread,1).detach(); thread(pause_thread,2).detach(); thread(pause_thread,3).detach(); cout << "Done spawning threads.\n";
cout << "(the main thread will now pause for 5 seconds)\n";
pause_thread(5);
return 0; }
|
获取线程ID和CPU核心数
void func() { this_thread::sleep_for (chrono::seconds(1)); cout << "func id = " << this_thread::get_id() << endl; }
int main() { thread t(func); cout << "t.get_id() = " << t.get_id() << endl; cout << "main id = "<<this_thread::get_id() << endl; cout << "cup num = " << thread::hardware_concurrency() << endl;
t.join();
return 0; }
|
互斥量
为什么需要互斥量
- 在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源。这个过程有点类似于,公司部门里,我在使用着打印机打印东西的同时(还没有打印完),别人刚好也在此刻使用打印机打印东西,如果不做任何处理的话,打印出来的东西肯定是错乱的。
void printer(const char *str) { while(*str != '\0') { cout << *str; str++; this_thread::sleep_for (chrono::seconds(1)); } cout << endl; }
void func1() { const char *str = "hello"; printer(str); }
void func2() { const char *str = "world"; printer(str); }
int main(void) { thread t1(func1); thread t2(func2);
t1.join(); t2.join();
return 0; }
|
独占互斥量std::mutex
- 互斥量的基本接口很相似,一般用法是通过lock()方法来阻塞线程,直到获得互斥量的所有权为止。在线程获得互斥量并完成任务之后,就必须使用unlock()来解除对互斥量的占用,lock()和unlock()必须成对出现。try_lock()尝试锁定互斥量,如果成功则返回true, 如果失败则返回false,它是非阻塞的。
mutex g_lock;
void printer(const char *str) { g_lock.lock(); while(*str != '\0') { cout << *str; str++; this_thread::sleep_for (chrono::seconds(1)); } cout << endl;
g_lock.unlock(); }
void func1() { const char *str = "hello"; printer(str); }
void func2() { const char *str = "world"; printer(str); }
int main(void) { thread t1(func1); thread t2(func2);
t1.join(); t2.join();
return 0; }
|
- 使用std::lock_guard可以简化lock/unlock的写法,同时也更安全,因为lock_guard在构造时会自动锁定互斥量,而在退出作用域后进行析构时就会自动解锁,从而避免忘了unlock操作。
mutex g_lock;
void printer(const char *str) { lock_guard<std::mutex> locker(g_lock); while(*str != '\0') { cout << *str; str++; this_thread::sleep_for (chrono::seconds(1)); } cout << endl;
}
|
原子操作
- 所谓的原子操作,取的就是“原子是最小的、不可分割的最小个体”的意义,它表示在多个线程访问同一个全局资源的时候,能够确保所有其他的线程都不在同一时间内访问相同的资源。也就是他确保了在同一时刻只有唯一的线程对这个资源进行访问。这有点类似互斥对象对共享资源的访问的保护,但是原子操作更加接近底层,因而效率更高。
//全局的结果数据
long total = 0;
//点击函数
void func()
{
for(int i = 0; i < 1000000; ++i)
{
// 对全局数据进行无锁访问
total += 1;
}
}
int main()
{
clock_t start = clock(); // 计时开始
//线程
thread t1(func);
thread t2(func);
t1.join();
t2.join();
clock_t end = clock(); // 计时结束
cout << "total = " << total << endl;
cout << "time = " << end-start << " ms\n";
return 0;
}
- 运行结果如下:
- 由于线程间对数据的竞争而导致每次运行的结果都不一样。因此,为了防止数据竞争问题,我们需要对total进行原子操作。
- 通过互斥锁进行原子操作: ```cpp //全局的结果数据 long total = 0; mutex g_lock;
//点击函数 void func() { for(int i = 0; i < 1000000; ++i) { g_lock.lock(); //加锁 total += 1; g_lock.unlock(); //解锁 } }
int main() { clock_t start = clock(); // 计时开始
//线程 thread t1(func); thread t2(func);
t1.join(); t2.join();
clock_t end = clock(); // 计时结束
cout << "total = " << total << endl; cout << "time = " << end-start << " ms\n";
return 0; }
|
atomic<long> total = {0};
void func() { for(int i = 0; i < 1000000; ++i) { total += 1; } }
int main() { clock_t start = clock();
thread t1(func); thread t2(func);
t1.join(); t2.join();
clock_t end = clock();
cout << "total = " << total << endl; cout << "time = " << end-start << " ms\n";
return 0; }
|