C++ 11 新特性
新类型
C++11 中新增long long 类型和unsigned long long ,以支持64位的整型
#include <iostream>
int main()
{
long long a = 3LL;
unsigned long long b = 4ULL;
printf("long size = %d\n", sizeof(long)); // 4
printf("long long size = %d\n", sizeof(long long)); // 8
printf("unsigned long long size = %d\n", sizeof(unsigned long long)); // 8
}
新增类型char16_t 和char32_t,以支持16位和32位的字符表示,均为无符号数,可能随系统不同而不同
#include <iostream>
int main()
{
char16_t c = u'A'; // 16位Unicode字符
char32_t c1 = U'A'; // 32位Unicode字符
printf("char16_t size = %d\n", sizeof(c)); // 2
printf("char32_t size = %d\n", sizeof(c1)); // 4
return 0;
}
新增原始字符串
#include <iostream>
#include <string>
int main()
{
char str1[] = "helloWorld\n";
char str2[] = R"(helloWorld\n)"; // 所见即所得
printf("str1 = %s", str1);
printf("str2 = %s", str2);
return 0;
}
统一的初始化
C++11扩大了用大括号括起的初始化列表的使用范围,使其可用于所有内置类型和用户定义的类型(即类对象)。使用初始化列表时,可添加等号(=),也可不添加。
基本类型的初始化
#include <iostream>
int main()
{
int x = {5};
double y {3.14};
short arr[5] {1, 2, 3, 4, 5};
char *str = new char[10] {'a', 'b', 'c', 'd'};
printf("x = %d\n", x);
printf("y = %f\n", y);
printf("arr[0] = %d\n", arr[0]);
printf("str = %s\n", str);
}
类的初始化
#include <iostream>
class MyClass
{
private:
int length;
double width;
public:
MyClass(int l, double w): length(l), width(w) {}
};
int main()
{
MyClass obj(10, 5.5); // old c++
MyClass obj1 {100, 20.2}; // c++ 11
MyClass obj2 = {1000, 30.3}; // c++ 11
}
如果类有将模板std::initializer_list
作为参数的构造函数,则只有该构造函数可以使用列表初始化形式。我们在使用vector等STL时候,发现它的初始化列表可以是任意长度。
#include <iostream>
#include <vector>
class MyVector
{
private:
std::vector<int> vec;
public:
MyVector(std::initializer_list<int> lst)
{
for (const auto&item: lst)
{
vec.push_back(item);
}
}
void print()
{
for (const auto&item : vec)
{
std::cout << item << " ";
}
}
};
int main()
{
MyVector v {1, 2, 3, 4, 5, 6};
v.print();
return 0;
}
std::initializer_list
除了作为构造函数外,还可以用于函数
#include <iostream>
double sum(std::initializer_list<double> lst)
{
double sum = 0;
for (auto it=lst.begin(); it!=lst.end(); ++ it)
{
sum += *it;
}
return sum;
}
int main()
{
double res = sum({1, 1, 2, 3, 5, 8, 13});
printf("res = %lf\n", res);
return 0;
}
缩窄
初始化列表语法可以防止缩窄,即禁止将数值赋给无法存储它的数值变量
#include <iostream>
int main()
{
char c = 1.57; // warn
printf("c = %d\n", c);
char c1 {1.57}; // error
printf("c1 = %d\n", c1);
return 0;
}
声明
auto
C++11提供了多种简化声明的功能,尤其在使用模板时。
以前,关键字auto是一个存储类型说明符,c++11将其用于实现自动类型推断
#include <iostream>
double fm(double, int)
{
return 1.1;
}
int main()
{
// auto int i = 10; // 原来的用法 由于只有这一种用法 被c++11将这个关键字拿来用了
auto i = 10;
auto pt = &i;
auto pf = fm;
char c = 'A';
printf("type of c = %s\n", typeid(c).name());
return 0;
}
auto简化模板声明
#include <iostream>
int main()
{
std::initializer_list<int> lst{1, 2, 3, 4, 5};
for (std::initializer_list<int>::iterator it = lst.begin(); it != lst.end(); ++ it)
{
printf("val = %d\n", *it);
}
for (auto it = lst.begin(); it != lst.end(); ++ it)
{
printf("val = %d\n", *it);
}
return 0;
}
decltype
如果我仅仅想推到类型而不产生变量怎么处理,关键字decltype将变量的类型声明为表达式指定的类型。
double x;
int n;
decltype (x*n) q;
decltype (&x) pd;
这在定义模板时特别有用,因为只有等到模板被实例化时才能确定类型
template <typename T, typename U>
void ef(T t,U u)
{
decltype(t*u) tu;
//...
}
nullptr
nullptr是c++11用来表示空指针新引入的常量值,在c++中如果表示空指针语义时建议使用nullptr而不要使用NULL,因为NULL本质上是个int型的0,其实不是个指针
#include <iostream>
void func(void *ptr){
std::cout << "func ptr" << std::endl;
}
void func(int i){
std::cout << "func i" << std::endl;
}
int main()
{
func(NULL); // call of overloaded 'func(NULL)' is ambiguous
func(nullptr);
return 0;
}
智能指针
如果在程序中使用new从堆中分配内存,等到不再使用时,应使用delete将其释放掉。但这个过程还是挺繁琐,稍不注意就会造成内存泄漏。所以c++11给我们引入了智能指针auto_ptr,来帮助我们自动完成这个过程。基于程序员的编程体验和BOOST库提供的解决方案。c++11抛弃了auto_ptr,并新增了unique_ptr、shared_ptr、weak_ptr。
存在问题
#include <iostream>
void div(int b)
{
int *a = new int(5);
if (b == 0)
throw std::invalid_argument("b can not equal to 0");
printf("res = %d", *a / b);
}
int main()
{
div(10);
return 0;
}
当main函数结束的时候,所占的内存被释放。如果指向的内存也被释放多好。如果是一个有析构函数的类对象,则可以在对象过期的时候,让析构函数删除指向的内存。这正是auto_ptr,unique_ptr,shared_ptr背后的思想。
自己编写一个类来实现智能指针
#include <iostream>
template<typename T>
class SmartPtr{
private:
T* _ptr;
public:
SmartPtr(T *ptr): _ptr(ptr) {
printf("Created!\n");
}
~SmartPtr(){
printf("deleted!\n");
delete _ptr;
}
T& operator*(){
return *_ptr;
}
T* operator->(){
return _ptr;
}
};
auto_ptr存在的问题
-
指向同一段内存,存在两次析构的问题
std::string *s1 = new std::string("abc"); std::auto_ptr<std::string> ap1(s1); std::auto_ptr<std::string> ap2(s1); std::cout << *ap1 << std::endl; std::cout << *ap2 << std::endl;
-
所有权转移问题
std::string *s1 = new std::string("abc");
std::auto_ptr<std::string> ap1(s1);
std::auto_ptr<std::string> ap2;
ap2 = ap1;
std::cout << *ap2 << std::endl;
unique_ptr
unique_ptr
是作用域指针,当超出作用域时,它会被销毁然后调用delete;
其不可复制,因为这会导致两个指针指向相同的内存,当其中一个将内存释放后,另一个便会指向已经被释放的内存,这会带来问题
要使用智能指针我们需要引入memory
头文件
std::unique_ptr<Entity> entity1(new Entity());
std::unique_ptr<Entity> entity2 = std::make_unique<Entity>();
以上是两种unique_ptr
的使用方式,后者对异常处理更加友好
需要注意的是unique_ptr
的构造函数是explicit
的,需要显式调用
使用unique_ptr
声明的entity1
和entity2
都会在作用域结束时被销毁
unique_ptr
是最简单的智能指针,是有用且低开销的,缺点是其不能复制
std::unique_ptr<Entity> entity3 = entity2;
<-为了防止我们写出这种代码,尝试去复制unique_ptr
,unique_ptr
的复制构造函数和=
操作符都被删除了,这样的代码会直接报错
shared_ptr
shared_ptr
是共享指针,在大部分编译器中,其通过引用计数实现了智能指针的共享,每当共享指针被复制时,其内部的引用计数便会加一,当共享指针被释放时,引用计数便会减一,当引用数量为0时,共享指针才会真正释放其指向的内存
std::shared_ptr<Entity> entity1(new Entity());
std::shared_ptr<Entity> entity2 = std::make_shared<Entity>();
我们仍有两种方式使用shared_ptr
,但对于shared_ptr
来说,后者明显更好
在unique_ptr
中使用make_unique
带来的优势仅仅是会抛出异常,但对于shared_ptr
来说,因为其需要声明一块专用的控制块内存用于存储应用计数,通过new
进行内存分配再传递给shared_ptr
会带来两次内存分配,而使用make_shared
则可以将两次内存分配组合在一起,获得更好的效率
weak_ptr
weak_ptr
通常和shared_ptr
一起使用,weak_ptr
可以被复制,但是同时不会增加引用计数,仅仅声明这个指针还活着
同时weak_ptr
可以查询其指向的内存块是否被释放了
std::weak_ptr<Entity> weak = entity2;
什么时候应该使用智能指针
根据实际情况而定,当你不需要手动管理内存时,智能指针会非常方便
在使用智能指针时,unique_ptr
应该被优先考虑,因为其几乎没有开销,而当我们需要共享数据而unique_ptr
无法实现时,shared_ptr
就应该被使用了,即使其会带来一些额外的开销。
lambda表达式
lambda的概念
lambda表达式本质是一个匿名函数(因为它没有名字),恰当使用lambda表达式可以让代码变得简洁.并且可以提高代码的可读性
简单体会一下:
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
std::vector<int> lst{1, 2, 3, 4, 5};
std::sort(lst.begin(), lst.end(), [](int x, int y){
return x>y;
});
for (const auto& item:lst){
std::cout << item << " ";
}
return 0;
}
lambda的格式
[capture-list] (parameters) mutable -> return-type {statement };
[ 捕捉列表 ] ( 形参 ) 约束(可选) -> 返回值类型(可选) {函数体}
-
[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量给lambda函数使用,如果没有变量需要捕捉,那[]里面的内容可以不写
-
(parameters):参数列表, 与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
-
mutable:默认情况下,lambda函数总是一个const函数,(如果以传值方式方式捕获变量,是不可以修改的),但是可以使用mutable可以取消其常量性, 使用该修饰符时,参数列表不可省略(即使没有参数,也需要带上参数列表)
-
return-type:返回值类型, 用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略,返回值类型明确情况下也可省略,由编译器对返回类型进行推导,所以一般不写返回值
-
{statement}:函数体, 在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量
lambda函数的参数列表和返回值类型都是可选部分,但捕捉列表和函数体是不可省略的,所以最简单的lambda为
**[]{}**
-
捕获列表
关于捕获列表
捕获列表描述了上下文中哪些数据变量可以被lambda函数使用,以及使用的方式是传值还是传引用
[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量
lambda具体用法举例
-
最简单的lambda
#include <iostream> int main() { []{printf("Hello, World!");}(); return 0; }
-
值传递&引用传递
#include <iostream> using namespace std; int main() { int a = 20; cout << "原始地址 = " << &a << endl; [=]{cout << "lambda val: " << &a << endl;}(); [&]{cout << "lambda ref: " << &a << endl;}(); return 0; }
-
值传递配合引用传递
#include <iostream> using namespace std; int main(){ int a = 20; int b = 10; cout << "outer &a = " << &a << endl; cout << "outer &b = " << &b << endl; [=, &a](){ cout << "&a = " << &a << endl; cout << "&b = " << &b << endl;}(); [=, &b](){ cout << "&a = " << &a << endl; cout << "&b = " << &b << endl;}(); return 0; }
-
mutable使用
#include <iostream> int main() { int a = 10; [=]() mutable { a = 20; std::cout << "a = " << a << std::endl; }(); return 0; }
-
常用案例
#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> lst{5, 4, 3, 2, 1}; std::sort(lst.begin(), lst.end(), [](int x, int y){return x < y;}); std::for_each(lst.begin(), lst.end(), [](int v){std::cout << v << std::endl;}); return 0; }
右值引用
右值引用是 C++11 引入的与 Lambda 表达式齐名的重要特性之一。它的引入解决了 C++ 中大量的历史遗留问题,消除了诸如 std::vector、std::string 之类的额外开销.提升了c++的效率。
左值和右值的概念
左值:一个表示数据的表达式(如变量名或解引用的指针),且可以获取他的地址(取地址),可以对它进行赋值;它可以在赋值符号的左边或者右边。
右值:一个表示数据的表达式(如字面常量、函数的返回值、表达式的返回值),且不可以获取他的地址(取地址);它只能在赋值符号的右边。
- 纯右值(prvalue, pure rvalue),纯粹的右值,要么是纯粹的字面量,例如 10, true;要么是求值结果相当于字面量或匿名临时对象,例如 1+2。非引用返回的临时变量、运算表达式产生的临时变量、原始字面量、Lambda 表达式都属于纯右值。
- 将亡值(xvalue, expiring value),是 C++11 为了引入右值引用而提出的概念(因此在传统 C++中,纯右值和右值是统一个概念),也就是即将被销毁、却能够被移动的值。
将亡值可能稍有些难以理解,我们来看这样的代码:
#include <iostream>
using namespace std;
int func(int x, int y) { return x + y; }
int main() {
int a = 10;
int b = a;
const int d = 40; //右值具有const属性,临时值
cout << &d << endl;
//右值
10;
a+b;
x + y;
func(x, y);
cout << &func(x, y) << endl; //不能取地址
return 0;
}
左值引用和右值引用的区别
左值引用:给左值取别名 &
右值引用:给右值取别名 && ,右值引用的目的是为了延长用来初始化对象的生命周期
*std::move移动不了什么,唯一的功能是把左值强制转化为右值.
#include <algorithm>
#include <iostream>
using namespace std;
int func(int x, int y) { return x + y; }
int main() {
//左值引用
int a = 10;
int &la = a;
const int y = 10;
int& rx2 = y; // 非法:non-const引用不能被const左值初始化
// const左值引用既可以引用左值,也可以引用右值
const int b = 10;
const int &lb = b;
const int &lb1 = 10;
//右值引用只能右值不能左值
int &&r1 = 10;
//右值引用可以引用move以后的左值
int &&r2 = std::move(a);
return 0;
}
总结
左值基本上是具有存储属性的对象,其具有地址和值,可以出现在
=
的左右两边右值基本上是临时对象,如字面量与表达式,大部分情况下只能出现在
=
的右边,不能被赋值左值引用,如
std::string&
,只能接受左值,除非加上const
右值引用,如
std::sting&&
,只能接受右值
// 返回值为int&类型,是左值引用,所以只能返回左值,即必须是具有存储空间,不能是临时变量
// 接受的参数为string&类型,是左值引用,所以接受的参数也必须是左值,直接Print("Hmxs")会报错
int& Print(std::string& name)
{
static int value = 1;
return value;
}
int main()
{
std::string s = "Hmxs";
Print(s);
}
std::string name1 = "wzh";
std::string name2 = "hmxs";
std::string name3 = name1 + name2;
// name1,name2,name3是左值
// "wzh","hmxs",name1+name2是右值
// 参数为右值引用,只能传入右值,传入左值会报错,即Print(name)会报错
void Print(std::string&& name) { }
int main()
{
Print("wzh");
}
移动语义
移动语义是一种 C++ 中的编程技术,通过移动对象的资源(如动态分配的内存、文件句柄等)的所有权,以提高性能和避免不必要的资源复制。
std::move 是 C++ 标准库中的一个函数,用于将一个左值转换为右值引用,从而支持移动语义。移动语义允许在不进行深层复制的情况下转移资源的所有权,从一个对象转移到另一个对象。
具体来说,std::move 的作用是将给定的左值强制转换为右值引用,使得编译器可以选择调用移动构造函数而不是拷贝构造函数。这在一些情况下可以显著提高性能,尤其是涉及到资源管理的对象,比如动态分配的内存或文件句柄。
#include <iostream>
#include <cstring>
class String{
private:
char* m_data;
uint32_t m_size;
public:
String() = default;
// 构造函数
String(const char* string){
printf("Created!\n");
m_size = strlen(string);
m_data = new char[m_size];
memcpy(m_data, string, m_size);
}
// 拷贝构造
String(const String& string){
printf("Copied!\n");
m_size = string.m_size;
m_data = new char[m_size];
memcpy(m_data, string.m_data, m_size);
}
// 析构函数
~String(){
delete m_data;
}
void print(){
for (uint32_t i=0; i<m_size; ++i){
printf("%c", m_data[i]);
}
printf("\n");
}
};
class Entity{
private:
String _name;
public:
Entity(const String& name): _name(name) {}
void printName(){
_name.print();
}
};
int main()
{
Entity entity (String("cherno"));
entity.printName();
return 0;
}
移动构造函数
在上面这个例子中,如果我们不使用移动语义,那么复制对象几乎无法避免
但我们必须复制对象在这一情景下其实理应是荒谬的,我们的需求为将一个String对象放入Entity中
,但我们需要做的却是先在外部创建一个String对象,再将其深拷贝到Entity的String中
,为啥我们不能直接将外部创建的String
直接移动到Entity
中呢?而使用移动语义,我们便得以做到这点
移动语义实质上便是将深拷贝变为浅拷贝,我们需要编写移动构造函数,通过右值引用将对象强行标记为右值,即临时对象,后通过std::move
使我们在拷贝时调用移动构造函数,而非复制构造函数
#include <iostream>
#include <cstring>
class String{
private:
char* _data;
uint32_t _size;
public:
String() = default;
String(const char* string){
printf("Created!\n");
_size = strlen(string);
_data = new char[_size];
memcpy(_data, string, _size);
}
String(const String& string){
printf("Copied!\n");
_size = string._size;
_data = new char[_size];
memcpy(_data, string._data, _size);
}
// 移动构造函数
String(String&& string){
printf("Moved!\n");
_size = string._size;
_data = string._data;
// 处理原先的对象
string._size = 0;
string._data = nullptr;
}
~String(){
printf("Destoryed\n");
delete _data;
}
void print(){
for (uint32_t i=0; i<_size; ++i){
printf("%c", _data[i]);
}
printf("\n");
}
};
class Entity{
private:
String _name;
public:
Entity(const String& name): _name(name) {}
Entity(String&& name): _name(std::move(name)) {}
void printName(){
_name.print();
}
};
int main(){
Entity entity(String("cherno"));
entity.printName();
return 0;
}
通过上面的代码,我们便成功避免了多余的复制,其中Entity entity(String("Hmxs"));
具体的逻辑是:
- 首先
String("Hmxs")
会创建一个临时的String
对象,这一对象传入entity
的构造函数中 - 因为传入的
String
对象是右值,所以会优先进入Entity(String&& name)
这一参数为右值引用,并执行name_(std::move(name))
的初始化过程
作为右值引用被传入的String
对象在进入函数后变为了左值,所以需要使用std::move
将其强转为右值引用,才能进入String
的移动构造函数
- 之后便进入了
String
的移动构造函数中,将entity
中的name_
指向临时的String
对象,之后将临时的String
对象置空,即完成了移动过程
因为临时的String
对象是右值,且被置空,在语句结束后会自动地进行释放
至此,我们便完成了一次成功的移动语义,cool
移动赋值运算符
在上一章中,我们通过编写移动构造函数
实现了移动语义,而正如其名字中的构造
所言,其是一种类型的构造函数,只有在对象构造时才会被调用
那么如果我们想要对一个已经存在的对象使用移动语义呢?我们应该使用移动赋值操作符
#include <iostream>
#include <cstring>
class String{
private:
char* _data;
uint32_t _size;
public:
String() = default;
String(const char* data) {
printf("Created!\n");
_size = strlen(data);
_data = new char[_size];
memcpy(_data, data, _size);
}
String(const String& string) {
printf("Copied!\n");
_size = string._size;
_data = new char[_size];
memcpy(_data, string._data, _size);
}
String& operator=(const String& string) {
printf("assign!\n");
_size = string._size;
_data = new char[_size];
memcpy(_data, string._data, _size);
return *this;
}
~String(){
printf("Destroyed!\n");
delete _data;
}
// 移动构造函数
String(String&& string) {
printf("move copy!\n");
_size = string._size;
_data = string._data;
string._size = 0;
string._data = nullptr;
}
// 移动赋值函数
String& operator=(String&& string) {
printf("move assign!\n");
// 如果尝试给自己赋值,则直接返回
if (this == &string) {
return *this;
}
// 移动赋值操作会作用于一个已经存在的对象,我们应该先释放原先的对象
delete[] _data;
_data = string._data;
_size = string._size;
// 处理原先的对象
string._data = nullptr;
string._size = 0;
return *this;
}
};
int main() {
// String s("abc");
// String s1(std::move(s)); // move copy
String s("abc");
String dest("bcd");
dest = std::move(s); // move assign
return 0;
}
完美转发 std::forward
-
万能引用
模板中的&& , 不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
#include <iostream> template<typename T> void func(T && val){ std::cout << val << std::endl; } int main(){ int a1 = 3; // int &&a = a1; int &&b = 3; func(4); func(b); func(a1); func(std::move(a1)); return 0; }
-
完美转发
#include <iostream> template<typename T> void func(T &val){ std::cout << "左值引用: " << val << std::endl; } template<typename T> void func(T && val){ std::cout << "右值引用: " << val << std::endl; } template <typename T> void func1(T && val) { func(std::forward<T>(val)); } int main() { int a = 10; func1(a); func1(10); func1(std::move(a)); return 0; }
可调用对象
一组执行任务的语句都可以视为一个"函数",一个可调用对象。
在C++中就func的类型可以为:
- 普通函数
- 类成员函数
- 类静态函数
- 仿函数
- 函数指针
- lambda表达式 C++11加入标准
普通函数
#include <iostream>
typedef int(* FUNC)(int, int);
using FUNC1 = int(*)(int, int);
int func(int x, int y) {
return x + y;
}
int main() {
FUNC f1 = func;
FUNC1 f2 = func;
std::cout << f1(5, 5) << std::endl;
std::cout << f2(5, 10) << std::endl;
return 0;
}
类成员函数
#include <iostream>
class MyClass{
public:
int add(int x, int y) {
return x + y;
}
};
typedef int(MyClass::*FUNC)(int, int);
using FUNC1 = int(MyClass::*)(int, int);
int main() {
FUNC1 f = MyClass::add;
MyClass m;
std::cout << (m.*f)(5, 5) << std::endl;
return 0;
}
类静态函数
#include <iostream>
class MyClass {
public:
static int add(int x, int y);
};
int MyClass::add(int x, int y) {
return x + y;
}
using FUNC = int(*)(int, int);
int main() {
FUNC f = MyClass::add;
std::cout << f(5, 5) << std::endl;
return 0;
}
仿函数
函数指针
lambda表达式
function和bind
function的概念
std::function是一个函数包装器模板,在c++11中,将function纳入标准库中,该函数包装器模板能包装任何类型的可调用元素.
一个std::function类型对象实例可以包装下列这几种可调用元素类型:函数、函数指针、类成员函数指针或任意类型的函数对象(例如定义了operator()操作并拥有函数闭包)
基本格式:
**function<return-type(type1,type2)> func**
作用:实现接口统一
function的使用
#include <iostream>
#include <functional>
#include <map>
#include <string>
int add(int x, int y) {
return x + y;
}
int sub(int x, int y) {
return x - y;
}
void func(std::function<int(int, int)> f, int x, int y) {
std::cout << f(x, y) << std::endl;
}
int main() {
std::function<int(int, int)> f1 = add;
std::function<int(int, int)> f2 = sub;
std::function<int(int, int)> f3 = [](int x, int y) { return x * y; };
func(f1, 5, 5);
func(f2, 5, 10);
func(f3, 5, 15);
std::map<std::string, std::function<int(int, int)>> m{ {"+", f1}, {"-", f2}, {"*", f3}};
std::cout << m["+"](10, 20) << std::endl;
return 0;
}
bind
bind是一个标准库函数,定义在functional头文件中。可以将bind函数看作一个通用的函数适配器,它接受一个可调用对象,生成新的可调用对象来适应原对象的参数列表。
template< class F, class... Args >
bind( F&& f, Args&&... args );
绑定普通函数
#include <iostream>
#include <functional>
class MyClass {
public:
MyClass() {
std::cout << "Created!" << std::endl;
}
MyClass(const MyClass& other) {
std::cout << "Copy!" << std::endl;
}
~MyClass() {
std::cout << "Destroy!" << std::endl;
}
int add(int x, int y) {
return x + y;
}
};
int main() {
using FUNC = int(MyClass::*)(int, int);
MyClass m;
FUNC f = &MyClass::add;
std::function<int(int)> func = std::bind(f, &m, std::placeholders::_1, 5);
std::cout << func(3) << std::endl;
return 0;
}
绑定类成员函数
#include <iostream>
#include <functional>
class MyClass {
public:
MyClass() {
std::cout << "Created!" << std::endl;
}
MyClass(const MyClass& other) {
std::cout << "Copy!" << std::endl;
}
~MyClass() {
std::cout << "Destroy!" << std::endl;
}
int add(int x, int y) {
return x + y;
}
};
int main() {
using FUNC = int(MyClass::*)(int, int);
MyClass m;
FUNC f = &MyClass::add;
std::function<int(int)> func = std::bind(f, &m, std::placeholders::_1, 5);
std::cout << func(3) << std::endl;
return 0;
}
绑定类静态成员函数
#include <iostream>
#include <functional>
class MyClass {
public:
MyClass() {
std::cout << "Created!" << std::endl;
}
MyClass(const MyClass& other) {
std::cout << "Copy!" << std::endl;
}
~MyClass() {
std::cout << "Destroy!" << std::endl;
}
static int add(int x, int y);
};
int MyClass::add(int x, int y) {
return x + y;
}
int main() {
using FUNC = int(MyClass::*)(int, int);
// 无需类的对象
auto f = std::bind(MyClass::add, 10, 10);
std::cout << f() << std::endl;
return 0;
}
std::ref
注意:
- bind预先绑定的参数需要传具体的变量或值进去,对于预先绑定的参数,是pass-by-value的;
- 对于不事先绑定的参数,需要传std::placeholders进去,从_1开始,依次递增。placeholder是pass-by-reference的;
如果要传递引用,则要使用std::ref()
#include <iostream>
#include <functional>
void func(int& x) {
++x;
std::cout << x << std::endl;
}
int main() {
int a = 10;
auto f = std::bind(func, std::ref(a));
f();
std::cout << a << std::endl;
return 0;
}
std::ref
std::ref主要在函数式编程(如std::bind)时使用,bind是对参数直接拷贝,无法传入引用(即使你传入的实参是引用类型也不行),故引入std::ref()。使用std::ref可以在模板传参的时候传入引用。
ref能使用reference_wrapper包装好的引用对象代替原本会被识别的值类型,而reference_wrapper能隐式转换为被引用的值的引用类型。
bind,如果要传递引用,则要使用std::ref()
#include <iostream>
#include <functional>
void func(int& x) {
++x;
std::cout << x << std::endl;
}
int main() {
int a = 10;
auto f = std::bind(func, std::ref(a));
f();
std::cout << a << std::endl;
return 0;
}