Advanced Cpp Note
工业级的Cpp语法
一、std::function和bind绑定器
可调用对象
- 是一个函数指针
- 是一个具有
operator()
成员函数的类对象(仿函数) - 是一个可被转换为函数指针的类对象
- 是一个类成员(函数)指针
可调用对象包装器std::funtion
- 头文件
<functional>
- 可以容纳除了类成员(函数)指针之外的所有可调用对象
- 可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟执行它们
function
比普通函数指针更灵活和便利
std::bind绑定器
- 头文件
<functional>
- 接受一个可调用对象,生成一个新的可调用对象
std::bind
用来将可调用对象与其参数一起进行绑定,绑定后的结果可以使用std::function
进行保存,并延迟调用到任何我们需要的时候- 作用
- 将可调用对象与其参数一起绑定成一个仿函数(functor)
- 将多元(参数个数为n)可调用对象转成一元或者(n-1)元可调用对象,却只绑定部分参数
1
2
3std::bind(output, 1, 2)(); // 输出:1 2
std::bind(output, std::placeholders::_1, 2)(1); // 输出 :1 2
std::bind(output, 2, std::placeholders::_1)(1); // 输出 :2 1
std::bind
的返回类型是一个stl内部定义的仿函数类型std::placeholders::_1
是一个占位符,代表这个位置将在函数调用时被传入的第一个参数所替代- 组合bind函数
std::bind(std::greater<int>(), std::placeholders::_1, 5);
- 判断是否大于5的功能闭包
C++11通过提供std::function和std::bind统一了可调用对象的各种操作
二、lambda表达式
语法形式
[ capture ] ( params ) opt -> ret { body; };
- C++11允许省略lambda表达式返回值定义,这样编译器就会根据
return
语句自动推导出返回类型- 初始化列表不能用于返回值的自动推导
- lambda表达式在没有参数列表时,参数列表是可以省略的
捕获列表
[]
不捕获任何变量[&]
捕获外部作用域中所有变量,并作为引用在函数体中使用(引用捕获)[=]
捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)[=, &foo]
按值捕获外部作用域中所有变量,并按引用捕获foo变量[bar]
按值捕获bar变量,同时不捕获其他变量[this]
捕获当前类中的指针,让lambda表达式拥有和当前类成员函数同样的访问权限。如果已经使用了&
或者=
,就默认添加此选项- 如果希望去修改按值捕获的外部变量,需要显示指明lambda表达式为
mutable
,如:auto f2 = [=]() mutable {return a++; }
可以认为它是个带有operator()的类,即仿函数
- 可以使用
std::function
和std::bind
来存储和操作lambda表达式1
2std::function<int(int)> f1 = [](int a){ return a; };
std::function<int(void)> f2 = std::bind([](int a){ return a; }, 123); - 对于没有捕获任何变量的lambda表达式,还可以被转换成一个普通的函数指针
1
2
3using func_t = int (*)(int);
func_t f = [](int a){ return a; };
f(123); - 捕获变量的lambda表达式则不能转化为普通指针,若转换,lambda表达式本身的this指针就丢失掉了
- lambda和
std::function
的效果是一样的,一般情况下可直接用lambda来代替function
三、tuple元组
定义
- 可以把它当作一个通用的结构体来用,不需要创建结构体又获取结构体的特征
- 在某些情况下可以取代结构体,使程序更简洁、直观
- 如果用tuple来替代3个以上字段的结构体时就不太合适了,不直观,易读性降低(建议)
功能
- 创建元组
tuple<const char*, int> tp = make_tuple(sendPack, nSendSize);
auto tp = return std::tie(1, "aa", 2);
// tp的实际类型是:std::tuple<int&, string&, int&>
- 获取元组的值
1
2const char* data = tp.get<0> (); // 获取第一个值
int len = tp.get<1> (); // 获取第二个值- 通过
std::tie
解包tuple1
2
3int x, y;
string a;
std::tie(x, a, y) = tp;- 如果只想了解某个位置的值时,可以用
std::ignore
占位符来表示不解某个位置的值
std::tie(std::ignore, std::ignore, y) = tp;
// 只解第3个值
- 如果只想了解某个位置的值时,可以用
- 通过
- 创建右值引用元组
forward_as_tuple
1
2std::map<int, std::string> m;
m.emplace(std::forward_as_tuple(10, std::string(20, 'a')));- 创建了类似于
std::tuple<int&&, std::string&&>
类型的tuple
- 创建了类似于
- 连接多个tuple
tuple_cat
四、shared_ptr共享的智能指针
概念
- 希望多个智能指针管理同一个资源就用shared_ptr
std::shared_ptr
使用引用计数,每一个shared_ptr的拷贝都指向相同的内存- 在最后一个shared_ptr析构的时候,内存才会被释放
基本用法
- 初始化
- 可以通过使用构造函数、
std::make_shared<int>
辅助函数和reset
方法来初始化shared_ptr1
2
3
4std::shared_ptr<int> p(new int(1)); // 使用动态初始化,后加一个括号为值初始化
std::shared_ptr<int> p2 = p;
std::shared_ptr<int> ptr;
ptr.reset(new int(1)); - 不能将一个原始指针直接赋值给智能指针
- 当智能指针中有值的时候,调用reset会使引用计数减1
- 可以通过使用构造函数、
- 获取原始指针
- 可以通过
get
方法来返回原始指针1
2std::shared_ptr<int> ptr(new int(1));
int* p = ptr.get();
- 可以通过
- 指定删除器
- 智能指针初始化可以指定删除器
- 当p的
引用计数
为0时,自动调用删除器DeleteIntPtr来释放对象的内存1
2
3
4
5void DeleteIntPtr(int* p)
{
delete p;
}
std::shared_ptr<int> p(new int, DeleteIntPtr); - 也可以用lambda表达式指定删除器
std::shared_ptr<int> p(new int, [](int* p) { delete p; });
- 当我们用
shared_ptr
管理动态数组时,需要指定删除器,因为std::shared_ptr
的默认删除器不支持数组对象 - 也可以将
std::default_delete
作为删除器
注意事项
- 1)不要用一个原始指针初始化多个shared_ptr
- 2)不要在函数实参中创建shared_ptr
- 可能会因为发生异常而泄露内存
- 3)通过
shared_from_this()
返回this指针- 因为this指针本质上是一个
裸指针
,因此这样可能会导致重复析构 - 解决方法
- 让目标类通过派生
std::enable_shared_from_this<T>
类,然后使用基类的成员函数shared_from_this
来返回this的shared_ptr
- 让目标类通过派生
- 因为this指针本质上是一个
- 4)要避免循环引用
- 智能指针最大的一个陷阱就是循环引用,循环引用会导致内存泄露
- 导致意外延长对象的生命期
- 解决方法
- 使用weak_ptr
五、unique_ptr独占的智能指针
概念
- 希望只有一个智能指针管理资源或者管理数组就用unique_ptr
- unique_ptr是一个独占型的智能指针
- 它不允许其他的智能指针共享其内部的指针
- 不允许通过赋值将一个unique_ptr赋值给另外一个unique_ptr
- 可以通过
std::move
来转移到其他的unique_ptr1
2unique_ptr<T> myPtr(new T);
unique_ptr<T> myOtherPtr = std:: move(myptr);
注意事项
- 如果是数组,则判断是否是定长数组
- 若为定长数组则编译不通过(不能这样调用
make_unique<T[10]>(10)
) - 若为非定长数组,则获取数组中的元素类型,再根据参数size创建动态数组的unique_ptr
unique_ptr<int[]> ptr5 = make_unique<int[]>(10);
- 若为定长数组则编译不通过(不能这样调用
- unique_ptr可以指向数组(而shared_ptr这么做不合法)
1
2std::unique_ptr<int []> ptr(new int[10])
ptr[9] = 9; // 设置最后一个元素值为9 - unique_ptr指定删除器的时候需要确定删除器的类型
std::unique_ptr<int, void(*)(int*)> ptr(new int (1), [](int* p) { delete p; });
- lambda表达式在没有捕获变量的情况下是可以直接转换为函数指针的,一旦捕获了就无法转换了
std::unique_ptr<int, void(*)(int*)> ptr(new int (1), [&](int* p) { delete p; });
// 错误,捕获了变量
- 如果希望unique_ptr删除器支持lambda可以这么写
std::unique_ptr<int, std::function<void(int*)>> ptr(new int(1), [&](int* p) { delete p; }
六、weak_ptr弱引用的智能指针
概念
- weak_ptr是用来监视shared_ptr的,不会使引用计数加1
- 它不管理shared_ptr内部的指针,主要是为了监视shared_ptr的生命周期
- 它的构造不会增加引用计数,它的析构也不会减少引用计数
基本用法
- 1)通过
use_count()
方法来获得当前观测资源的引用计数1
2
3shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
cout << wp.use_count() << endl; // 结果将输出1 - 2)通过
expired()
方法来判断所观测的资源是否已经释放(true为无效)1
2
3
4if (wp.expired())
std::cout << "weak_ptr无效,所监视的智能指针已经被释放";
else
std::cout << "weak_ptr有效"; - 3)通过
lock()
方法来获取所监视的shared_ptr1
2
3
4auto sp = std::make_shared<int> (42);
std::weak_ptr<int> gw = sp;
auto spt = gw.lock(); // 获取所监视的shared_ptr
cout << *spt << endl;
weak_ptr返回this指针
std::enable_shared_from_this
类中有一个weak_ptr,这个weak_ptr用来观测this智能指针- 调用
shared_from_this()
方法时,会调用内部这个weak_ptr的lock()
方法,将所观测的shared_ptr返回 - 获取自身智能指针的函数仅在
shared_ptr<T>
的构造函数被调用之后才能使用,因为enable_shared_from_this
内部的weak_ptr只有通过shared_ptr才能构造