当前位置: 首页 > news >正文

【C + +】C++11 (下) | 类新功能 + STL 变化 + 包装器全解析

 

🌟个人主页:第七序章  

🌈专栏系列:C++

目录

❄️前言:

☀️一、新的类功能

⭐1.1 移动语义注意项

⭐1.2 Rule of Five机制

⭐1.3 小结

☀️二、"强制生成"默认函数的关键字default

☀️三、"禁止生成"默认函数的关键字delete

☀️四、STL中一些变化

☀️五、包装器

⭐function

⭐bind 与占位符

☀️六、本文小结

🌻共勉:


❄️前言:

前面我们已经学习了C + + 11(中),今天我们来学习C + + 11(下)。

☀️一、新的类功能

在C++11前,C++类有六个默认成员函数(默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数 )

⭐1.1 移动语义注意项

C++11 新增了两个:移动构造函数和移动赋值运算符重载 。

如果没有显式实现移动构造或赋值函数,同时没有显式显式析构、拷贝、赋值重载函数中任意一个。编译器会自动生成默认移动构造。其中默认生成的移动构造或赋值函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员(注意是成员),则需要看这个成员是否实现移动构造或赋值,如果实现了就调用移动构造或赋值,没有实现就调用拷贝构造或赋值重载。

⭐1.2 Rule of Five机制

C++11 引入了五个特殊成员函数,其中有三对:

  1. 拷贝构造函数拷贝赋值运算符
  2. 移动构造函数移动赋值运算符
  3. 析构函数

当你定义其中的一个(如移动构造函数),编译器会认为你对这个类的资源管理有特殊的要求,因此不再生成默认的拷贝构造函数和拷贝赋值运算符,避免误用浅拷贝导致资源管理错误

析构函数和移动构造函数的不同角色:

  • 析构函数:用于销毁对象并释放其占用的资源。显式定义析构函数意味着你要自行控制资源的释放方式。C++ 假定你手动管理资源,因此不会为你生成其他依赖于默认资源管理的函数(如移动构造函数)
  • 移动构造函数:用于将资源从一个对象转移到另一个对象。它不负责销毁对象,而是将对象的资源"转交"给另一个对象。

移动构造函数主要是负责“转移”资源,而不是释放资源,编译器假设转移资源并不改变析构时的行为,所以它会继续生成默认析构函数,认为默认的资源释放机制(如自动销毁对象的成员)依然有效。对此当显示实现移动构造函数,编译器也会自动生成默认的析构函数,确保资源的销毁。

⭐1.3 小结

析构函数、拷贝构造、拷贝赋值重载是对于容器中深拷贝的类关于资源的管理,为了避免潜在的资源管理问题和不一致性,当你显式定义了析构函数时,编译器会尊重你的选择,不再生成默认的移动构造函数,需要你根据具体的类设计和资源管理策略,决定是否需要自定义移动构造函数(这三个特殊成员函数之间存在依赖的关系Rule of Five机制)。

// 以下代码在vs2013中不能体现,在vs2019下才能演示体现上面的特性。
class Person
{public:Person(const char* name = "", int age = 0):_name(name), _age(age){}/*Person(const Person& p)
:_name(p._name)
,_age(p._age)
{}*//*Person& operator=(const Person& p)
{
if(this != &p)
{
_name = p._name;
_age = p._age;
}
return *this;
}*//*~Person()
{}*/private://自定义成员bit::string _name;//内置类型成员int _age;
};
int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);Person s4;s4 = std::move(s2);return 0;
}

需要注意,关于移动语义是一种夺舍的行为,需要考虑被夺舍对象是否需要使用原本的资源,进行调用。

☀️二、"强制生成"默认函数的关键字default

C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成

特殊函数之间可能存在依赖或互斥的关系,编译器不会随意地插入可能与用户意图不符的代码,也就导致了当强制生成移动语句,编译器不会默认生成析构函数等与之依赖性强的函数,如果需要移动语句和拷贝函数等函数同时出现,建议全部进行强制生成。

因为自己去写的话,还是麻烦了一点,关于这些问题可以看成一个语法规定就好了。

☀️三、"禁止生成"默认函数的关键字delete

如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

如以下场景,这里类只能在堆上生成对象

class HeapOnly
{public:static HeapOnly* CreateObj(){return new HeapOnly;}//C++11HeapOnly(const HeapOnly&) = delete;//C++98 私有+只声明不实现private:HeapOnly(const HeapOnly&);HeapOnly(){}int _a = 1;
};
int main()
{//HeapOnly ho1;//HeapOnly* p1 = new HeapOnly;//以上是构造函数私有HeapOnly* p2 = HeapOnly::CreateObj();//尝试在堆上开辟空间// 不能被拷贝,才能禁止//HeapOnly obj(*p2);return 0;
}

分析几行代码:

  1. HeapOnly* p1 = new HeapOnly;这里构造函数是私有的
  2. HeapOnly obj(*p2);不能被拷贝,禁止拷贝构造函数,也是栈上开空间
  3. HeapOnly* p2 = HeapOnly::CreateObj();通过静态工厂方法在堆上创建对象

☀️四、STL中一些变化

  • 下图1圈起来的就是STL中的新容器,但是实际最有用的是unordered_mapunordered_set。这 两个我们前面已经进行了非常详细的讲解,其他的大家了解一下即可。
  • STL中容器的新接口也不少,最重要的就是右值引用和移动语义相关的push/insert/emplace系列 接口和移动构造和移动赋值,还有initializer_list版本的构造等,这些前面都讲过了,还有一些无关 痛痒的如cbegin/cend等需要时查查文档即可。
  • 容器的范围for遍历,这个在容器部分也讲过了。

☀️五、包装器

⭐function

function是一个类模板,也是一个包装器。std::function的实例化对象可以包装存储其它可以调用的对象:包括函数指针仿函数lambdabind表达式;

其中存储的对象被称为std::function的目标;如果std::function不含目标,那它为空(调用空目标导致抛出std::bad_function_call异常。

template <class T>
class function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;

以上是 function 的原型,他被定义头⽂件中

#include <functional>
int main()
{function<int(int, int)> func;func = [](int a, int b) { return a + b; };cout << func(3, 4) << endl; // 输出 7return 0;
}

function 的优势在于统一函数接口、做函数回调或作为参数传递。

函数指针、仿函数、lambda这些可以调用的对象的类型各不相同,std::function的优势就在于统一类型;

只要它们返回值,参数都相同,function就能对它们进行包装;这样在很多的地方就可以声明这些可调用对象的类型;

int fun(int a, int b)
{return a + b;
}
struct Fun
{
public:int operator() (int a, int b){return a + b;}
};
class Plus
{
public:Plus(int n = 10):_n(n){}static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return (a + b) * _n;}
private:int _n;
};
int main()
{function<int(int, int)> f1 = fun;//函数指针function<int(int, int)> f2 = Fun();//仿函数function<int(int, int)> f3 = [](int a, int b) {return a + b; };//lambdacout << f1(1, 1) << endl;cout << f2(1, 1) << endl;cout << f3(1, 1) << endl;//包装静态成员,需要指定类域并且使用&function<int(int, int)> f4 = &Plus::plusi;cout << f4(1, 1) << endl;//包装非静态成员函数时//这里还有一个隐藏的this指针,所以使用function包装后需要传对象或者对象的指针过去才能进行调用function<double(Plus*, double, double)> f5 = &Plus::plusd;Plus pd;cout << f5(&pd, 1.1, 1.1) << endl;function<double(Plus, double, double)> f6 = &Plus::plusd;cout << f6(pd, 1.1, 1.1) << endl;function<double(Plus&&, double, double)> f7 = &Plus::plusd;cout << f7(move(pd), 1.1, 1.1) << endl;cout << f7(Plus(), 1.1, 1.1) << endl;return 0;
}

这里再来看一道可以使用function包装优化的题目

150. 逆波兰表达式求值 - 力扣(LeetCode)

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;for(auto& e:tokens){if(e == "+"||e == "-" || e == "*" || e == "/"){int right = st.top();st.pop();int left = st.top();st.pop();switch(e[0]){case '+':st.push(left + right);break;case '-':st.push(left - right);break;case '*':st.push(left * right);break;case '/':st.push(left / right);break;}}else{st.push(stoi(e));}}return st.top();}
};

这道题,向上述这样写,特别难受好吧,我们可以使用function进行优化:

我们知道+-*/运算它返回值和参数类型都是相同的,那我们不妨将其包装起来;

然后使用map存储运算符和对应的函数调用对象

这样直接使用map[]就可以访问到要调用的函数/对象。

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;map<string,function<int(int,int)>> mp = {{"+",[](int x,int y){return x+y;}},{"-",[](int x,int y){return x-y;}},{"*",[](int x,int y){return x*y;}},{"/",[](int x,int y){return x/y;}}};for(auto& e:tokens){if(mp.count(e))//mp中存在就代表是操作符{int right = st.top();st.pop();int left = st.top();st.pop();st.push(mp[e](left,right));}elsest.push(stoi(e));}return st.top();}
};

这样我们代码看起来简洁了好多,用起来也很方便;

如果再多几个运算符,我们只需要在mp再新增即可。

从这个角度来看:lambda算是统一了那些可调用对象的类型,这样对于可调用对象(函数指针仿函数lambda),只要参数和返回值相同,那我们就可以使用function包装起来,方便调用。

⭐bind 与占位符

simple(1)
template <class Fn, class... Args>bind (Fn&& fn, Args&&... args);
with return type (2)
template <class Ret, class Fn, class... Args>bind (Fn&& fn, Args&&... args);
  • bind是一个函数模版,它也是一个可调用对象的包装器;简单来说它就是一个函数适配器,可以对接受的Fn可调用对象进行处理后返回一个可调用对象。
  • bind可以用来调整参数个数和参数的顺序。
  • bind也在这个头文件中。
int Fun(int a, int b)
{return (a - b) * 10;
}
int Func(int a, int b, int c)
{return (a - b - c) * 10;
}
int main()
{//这里_1始终指接受第一个实参//_2指接受第二个实参auto fun1 = bind(Fun, _1, _2);cout << fun1(10, 5) << endl;//fun1(10,5) -> Fun(10,5)auto Fun2 = bind(Fun, _2, _1);cout << Fun2(10, 5) << endl;//fun2(10,5) -> Fun(5,10)auto fun3 = bind(Fun, 100, _1);cout << fun3(5) << endl;//fun3(5) -> Fun(100,5)auto fun4 = bind(Fun, _1, 100);cout << fun4(5) << endl;//fun4(5) -> Fun(5,100)auto fun5 = bind(Func, 100, _1, _2);cout << fun5(5, 1) << endl;//fun5(5,1) -> Func(100,5,1)auto fun6 = bind(Func, _1, 100, _2);cout << fun6(5, 1) << endl;//fun6(5,1) -> Func(5,100,1)auto fun7 = bind(Func, _1, _2, 100);cout << fun7(5, 1) << endl;//fun7(5,1) -> Func(5,1,100);return 0;
}

调用bindauto newCallable = bind(callable, arg_list)(这里newCallable本身就是一个可调用对象,arg_list是参数列表,对应给定的callable的参数(也是可调用对象)

这样我们调用newCallablenewCallable就会调用callable,并传给它arg_list中的参数。

当我们使用function去包装类的非静态成员函数时,我们在调用时总是需要传该类型的对象或者该类型对象的指针,来完成调用;

这样的设计好难看,我每一次调用还要创建一个该类型的对象,那我还不如直接去调用呢

	function<double(Plus&&, double, double)> f1 = &Plus::plusd;Plus pd;cout << f1(move(pd), 1.1, 1.1) << endl;cout << f1(Plus(), 1.1, 1.1) << endl;

bind这个绑定,我们可以同来绑定一些固定的参数;

就比如这里需要传递该类类型的对象或者该类型对象的指针,我们使用bind绑定,直接锁死这个参数,那这样在调用时就不用显示传递了。

	function<double(double, double)> f2 = bind(&Plus::plusd, Plus(), _1, _2);cout << f2(1.1, 1.1) << endl;

☀️六、本文小结

主题分类核心知识点关键规则 / 特性说明注意事项
一、新的类功能1. 默认成员函数扩展(C++11 新增 2 个)- 原 6 个默认成员函数基础上,新增移动构造函数移动赋值运算符重载- 编译器自动生成条件:未显式实现移动相关函数,且未显式实现析构、拷贝构造、拷贝赋值中任意一个- 移动语义是 “资源转移”,需确认被转移对象后续是否使用- 仅 VS2019 + 等新编译器支持该特性,VS2013 不体现
2. Rule of Five(五大函数机制)- 包含:拷贝构造、拷贝赋值、移动构造、移动赋值、析构- 显式定义其中一个,编译器不再生成默认拷贝构造 / 赋值(避免浅拷贝错误)- 显式实现移动构造 / 赋值,编译器仍生成默认析构- 析构函数显式定义后,编译器不再生成默认移动构造- 五大函数需根据资源管理需求手动配套实现
二、默认函数控制关键字1. default(强制生成默认函数)- 用于显式指定编译器生成默认版本的函数- 场景:已实现拷贝构造,仍需默认移动构造时使用- 强制生成移动函数后,编译器不会自动生成析构、拷贝等依赖函数- 需同时生成多个默认函数时,建议全部用 default 显式指定
2. delete(禁止生成默认函数)- 用于禁止编译器生成默认版本的函数- 替代 C++98“私有 + 只声明不实现” 的方式,更简洁- 常用于限制拷贝(如只允许堆上创建对象的类)- 被 delete 修饰的函数无法被调用
三、STL 中的变化1. 新容器与接口扩展- 新增 unordered_map、unordered_set 等容器,其中前两者实用性最高- 容器新增右值引用相关接口(push/insert/emplace)、移动构造 / 赋值、initializer_list 构造等- 范围 for 遍历在容器中已支持- 其他新接口(如 cbegin/cend)可按需查阅文档
四、包装器1. function(可调用对象统一包装)- 统一函数指针、仿函数、lambda 等可调用对象的类型- 只要返回值和参数列表相同,即可用 function 包装- 需包含头文件<functional>- 包装非静态成员函数时,需考虑隐藏的 this 指针参数
2. bind 与占位符(函数适配器)- 可调整可调用对象的参数个数和顺序- 占位符_1_2等表示调用时传入的实参位置- 常用于简化非静态成员函数的调用(绑定对象或指针,减少参数传递)- 需配合<functional>头文件使用

🌻共勉:

以上就是本篇博客的所有内容,如果你觉得这篇博客对你有帮助的话,可以点赞收藏关注支持一波~~🥝


http://www.dtcms.com/a/566101.html

相关文章:

  • Linux的lsblk、fdisk和gdisk
  • 企业级业务平台项目设计、架构、业务全解之平台篇
  • 玩客云做网站建设网站的网站首页
  • 淘宝客导购网站怎么做运营推广seo招聘
  • 第一次全国水利普查公报的土壤保持部分
  • 爬虫数据清洗可视化链家房源
  • 2.1.1.HTML5
  • DP1363F 多协议NFC 兼容CLRC663开发资料
  • 2025-11-03 ZYZ28-NOIP模拟赛-Round1 hetao1733837的record
  • 建设网站怎样做如何利用互联网营销
  • Claude Code 原生安装教程
  • ps做游戏下载网站有哪些做网站备案都需要什么东西
  • Anaconda Prompt系统找不到指定路径
  • 聚类(Clustering)详解:让机器自己发现数据结构
  • cglib动态代理之MethodProxy
  • 网站公网安备链接怎么做网站建设需要多少资金
  • 重点专业建设网站网上接效果图平台
  • 海北网站建设队徽logo设计
  • 北京专业网站建设网站水头网站建设
  • Git hook pre-commit
  • 展示系统 网站模板广西新闻
  • Java基础语言进阶学习——2,对象创建机制与内存布局
  • 网站seo啥意思怎么做罗田县建设局网站
  • 基于yolov8的果蔬识别检测系统python源码+onnx模型+数据集+精美GUI界面
  • 网站建设合同电子版苏州网页制作报价
  • 廊坊网站建设方案策划民治做网站
  • 走路摆臂幅度大给人影响差-----坏习惯
  • TRO重磅消息 野生动物插画师Roger Hall跨境维权风暴来袭
  • ABB焊接机器人节气装置
  • Linux 孤儿进程和僵尸进程详解