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

C++11 类功能与包装器

一.C++11中新的类功能

1.默认的移动构造和移动赋值

1. 默认移动构造函数 (T(T&&))

行为:当一个类没有显式定义移动构造函数时,编译器在满足特定条件(见后文)时会自动生成一个默认的移动构造函数。

它的行为是:对其每个成员(包括基类成员)逐个进行移动初始化。

  • 对于内置类型(如 intdouble, 原始指针等):执行逐位拷贝(bitwise copy)。因为它们是“值”,没有“资源”可移动,所以拷贝和移动没有区别。

  • 对于类类型成员:调用该成员的移动构造函数

  • 对于数组成员:对数组中的每个元素执行上述规则。

class MyClass {
public:std::string str; // 类类型成员int num;         // 内置类型成员// 编译器为我们生成默认的移动构造函数,类似于:// MyClass(MyClass&& other) noexcept//     : str(std::move(other.str)), // 调用 string 的移动构造函数//       num(other.num)            // 内置类型,直接拷贝// {}
};int main() {MyClass obj1;obj1.str = "Hello";obj1.num = 42;MyClass obj2 = std::move(obj1); // 调用默认移动构造函数std::cout << obj2.str << std::endl; // 输出 "Hello"std::cout << obj2.num << std::endl; // 输出 42// obj1 的 str 已被“掏空”,处于有效但未指定的状态(通常是空字符串)std::cout << obj1.str.size() << std::endl; // 很可能输出 0// obj1.num 的值不变,因为它只是被拷贝了std::cout << obj1.num << std::endl; // 输出 42return 0;
}

2. 默认移动赋值运算符 (T& operator=(T&&))

行为:

同样地,当没有显式定义时,编译器在满足条件时会生成默认的移动赋值运算符。

它的行为是:对其每个成员(包括基类成员)逐个进行移动赋值。

  • 释放当前对象(*this)拥有的资源(通过调用各成员的析构函数或赋值运算符)。

  • 从源对象(右值引用)中“窃取”资源。

  • 返回 *this 的引用。

class MyClass {
public:std::string str;int num;// 编译器为我们生成默认的移动赋值运算符,类似于:// MyClass& operator=(MyClass&& other) noexcept {//     str = std::move(other.str); // 调用 string 的移动赋值运算符//     num = other.num;           // 内置类型,直接赋值//     return *this;// }
};int main() {MyClass obj1;obj1.str = "Hello";obj1.num = 42;MyClass obj2;obj2 = std::move(obj1); // 调用默认移动赋值运算符// 效果与移动构造函数类似:obj2 获得资源,obj1 被“掏空”return 0;
}

3.特点与关键点

  1. 自动生成的条件 (Rule of Five/The Rule of Zero)

    • 编译器不会总是自动生成移动操作。

    • 生成条件:只有在用户没有显式定义拷贝操作移动操作析构函数中的任何一个时,编译器才会自动生成默认的移动构造函数和移动赋值运算符。

    • 背后的逻辑:如果你定义了析构函数或拷贝操作,通常意味着这个类需要管理某种资源,编译器无法确定默认的“逐个成员移动”行为是否正确和安全,因此它选择不生成,将选择权交给程序员。此时,移动操作会回退为拷贝操作(如果拷贝操作可用),这可能是低效的。

  2. noexcept 说明符

    • 编译器生成的默认移动操作通常被标记为 noexcept(不抛出异常)。

    • 这非常重要,因为标准库容器(如 std::vector)在重新分配内存时,如果元素的移动构造函数是 noexcept 的,它会优先使用移动而不是拷贝来保证强异常安全。如果不是 noexcept,则会使用更保守的拷贝操作。

  3. 源对象状态

    • 移动操作后,源对象(被移动的对象)的状态是“有效但未指定的”(valid but unspecified)。你不能再对它的值做任何假设,但可以对其进行析构或重新赋值。

    • 对于像 std::string 或 std::vector 这样的标准库类型,移动后它们通常处于空状态(.empty() == true)。

    • 对于内置类型,移动操作等同于拷贝,所以值保持不变。

  4. 与拷贝操作的区别

    特性拷贝操作移动操作
    目的创建资源的独立副本“窃取”资源,所有权转移
    性能开销大(深拷贝)开销小(指针交换等)
    源对象操作后保持不变操作后处于“有效但未指定”状态
    参数const T& (常量左值引用)T&& (右值引用)

当你的类管理着动态资源(如原始指针、文件句柄等),而默认的“逐个成员移动”行为不正确时(例如,默认移动一个原始指针只是拷贝了指针值,会导致双重释放),你需要自己定义移动操作来实现资源的正确转移,并将源对象的资源指针置为 nullptr

现代 C++ 的最佳实践是使用 RAII 原则,用智能指针(std::unique_ptrstd::shared_ptr)和标准库容器来管理资源。这些类已经完美实现了移动语义,因此你通常不需要自己定义析构函数、拷贝/移动操作(遵循 The Rule of Zero),编译器生成的默认行为就是正确且高效的。

总结

特性默认移动构造函数默认移动赋值运算符
行为对每个成员进行移动初始化对每个成员进行移动赋值
生成条件用户未定义五巨头(拷贝构造、拷贝赋值、移动构造、移动赋值、析构)中的任何一个同上
异常规范noexceptnoexcept
优点高效,避免不必要的拷贝同上
缺点对管理原始资源的类不安全(浅拷贝问题)同上
最佳实践使用 RAII 对象管理资源,依赖编译器生成的默认操作(Rule of Zero)同上

2.default与delete

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

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

class Person
{
public:Person(const char* name = "张三", int age = 1):_name(name), _age(age){}Person(const Person& p) = default;Person(Person&& p) = default;~Person(){}
private:wjh::string _name;int _age;
};void func(ostream& out)
{}

二.包装器与绑定

1.包装器

包装器function是一种类模板,它主要用于接收各种可调用的类型,例如函数,类模板,lambda表达式等,但要注意参数类型要与可调用类型相匹配,例如:

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 = f;function<int(int, int)> f2 = Functor();function<int(int, int)> f3 = [](int a, int b) {return a + b; };cout << 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;function<double(Plus*, double, double)> f5 = &Plus::plusd;Plus pl;cout << f5(&pl, 1.111, 1.1) << endl;function<double(Plus, double, double)> f6 = &Plus::plusd;cout << f6(pl, 1.1, 1.1) << endl;cout << f6(Plus(), 1.1, 1.1) << endl;function<double(Plus&&, double, double)> f7 = &Plus::plusd;cout << f7(move(pl), 1.1, 1.1) << endl;cout << f7(Plus(), 1.1, 1.1) << endl;map<string, function<int(int, int)>> opFuncMap = {{"+", [](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; }},{"&", [](int x, int y) {return x + y; }},{"|", [](int x, int y) {return x | y; }},{"^", [](int x, int y) {return x ^ y; }}};return 0;
}

主要用途:

  1. 统一类型:可以将函数指针、lambda、仿函数(重载了 operator() 的类)、std::bind 表达式等所有可调用对象赋值给同一个 std::function 类型,消除了它们的类型差异。

  2. 延迟调用:可以将可调用对象存储起来,在未来的某个时刻再调用它。这在实现回调函数、事件驱动系统、消息队列时非常有用。

  3. 作为函数参数:函数可以使用 std::function 作为参数来接收外部传入的可调用策略,使函数接口更加通用和灵活,而无需使用函数指针或模板(模板会导致代码膨胀)。

2.绑定

std::bind 是一个函数模板,它像一个通用的函数适配器。它接受一个可调用对象,并生成一个新的可调用对象,这个新对象可以“绑定”原对象的部分参数,或者重新排列参数的顺序。这里最直接的应用就是调整参数顺序或者绑定死某个参数。例如上面对类成员函数的包装,第一个参数需要是this指针,我们只需要将这个参数绑死就不需要再传入了。

auto sub1 = bind(Sub, _1, _2);
cout << sub1(10, 5) << endl;auto sub2 = bind(Sub, _2, _1);
cout << sub2(10, 5) << endl;// 调整参数个数 (常用)
auto sub3 = bind(Sub, 100, _1);
cout << sub3(5) << endl;auto sub4 = bind(Sub, _1, 100);
cout << sub4(5) << endl;// 分别绑死第123个参数
auto sub5 = bind(SubX, 100, _1, _2);
cout << sub5(5, 1) << endl;
auto sub6 = bind(SubX, _1, 100, _2);
cout << sub6(5, 1) << endl;
auto sub7 = bind(SubX, _1, _2, 100);
cout << sub7(5, 1) << endl;// 成员函数对象进行绑死,就不需要每次都传递了
*function<double(Plus&&, double, double)> f6 = &Plus::plusd;
Plus pd;
cout << f6(move(pd), 1.1, 1.1) << endl;
cout << f6(Plus(), 1.1, 1.1) << endl;*/function<double(double, double)> f6 = bind(&Plus::plusd, Plus(), _1, _2);
cout << f6(1.1, 1.1) << endl;

3.包装器与绑定的简单应用

逆波兰表达式

传统方法实现

 //传统⽅式的实现
class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;for (auto& str : tokens){if(str == "+" || str == "-" || str == "*" || str == "/"){int right = st.top();st.pop();int left = st.top();st.pop();switch (str[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(str));}} return st.top();}
};

使用映射+function实现

// 使⽤map映射string和function的⽅式实现
// 这种⽅式的最⼤优势之⼀是⽅便扩展,假设还有其他运算,我们增加map中的映射即可
class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;// function作为map的映射可调⽤对象的类型map<string, function<int(int, int)>> opFuncMap = {{"+", [](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 & str : tokens){if (opFuncMap.count(str)) // 操作符{int right = st.top();st.pop();int left = st.top();st.pop();int ret = opFuncMap[str](left, right);st.push(ret);} else{st.push(stoi(str));}} return st.top();}
}


文章转载自:

http://SyUWm7Ip.hwLmy.cn
http://G7FDpQCX.hwLmy.cn
http://4J4WvosM.hwLmy.cn
http://N8qcAdQC.hwLmy.cn
http://9g1KdXwb.hwLmy.cn
http://lrRdufra.hwLmy.cn
http://GbVjgfLz.hwLmy.cn
http://pqu3WtWd.hwLmy.cn
http://R1PPWsmG.hwLmy.cn
http://ggJIls4X.hwLmy.cn
http://mT0eoFr7.hwLmy.cn
http://VTFQzdRi.hwLmy.cn
http://YE49skTa.hwLmy.cn
http://qdu4fwYI.hwLmy.cn
http://wdhX6Lhu.hwLmy.cn
http://76yfw7Zl.hwLmy.cn
http://NiK0chRe.hwLmy.cn
http://Ckz8Xcuy.hwLmy.cn
http://JzZzJYfZ.hwLmy.cn
http://BPYcsgOu.hwLmy.cn
http://ScrfV5io.hwLmy.cn
http://pwJ9rOum.hwLmy.cn
http://Dsdk1ujG.hwLmy.cn
http://kT0E6RJE.hwLmy.cn
http://1vALdLm4.hwLmy.cn
http://xJMSHemw.hwLmy.cn
http://qRbbKqWZ.hwLmy.cn
http://of3ujv8W.hwLmy.cn
http://2YQiB8OH.hwLmy.cn
http://LoIU9bV5.hwLmy.cn
http://www.dtcms.com/a/367292.html

相关文章:

  • Qt---connect建立对象间的通信链路
  • vLLM显存逆向计算:如何得到最优gpu-memory-utilization参数
  • 第15章 Jenkins最佳实践
  • 【倒计时2个月】好•真题资源+专业•练习平台=高效备赛2025初中古诗文大会
  • openEuler2403安装部署Kafbat
  • matlab 数据分析教程
  • git还原操作
  • Spring Cloud OpenFeign 核心原理
  • 【华为培训笔记】OptiX OSN 9600 设备保护专题
  • 解决 ES 模块与 CommonJS 模块互操作性的关键开关esModuleInterop
  • 解密llama.cpp:Prompt Processing如何实现高效推理?
  • 抽象与接口——Java的“武器模板”与“装备词条”
  • 数组本身的深入解析
  • Linux Centos7搭建LDAP服务(解决设置密码生成密文添加到配置文件配置后输入密码验证报错)
  • 记录一下tab梯形圆角的开发解决方案
  • java面试中经常会问到的dubbo问题有哪些(基础版)
  • illustrator-04
  • 观察者模式-红绿灯案例
  • 【LLM】FastMCP v2 :让模型交互更智能
  • Linux下开源邮件系统Postfix+Extmail+Extman环境部署记录
  • 在Anaconda下安装GPU版本的Pytorch的超详细步骤
  • 追觅科技举办2025「敢梦敢为」发布会,发布超30款全场景重磅新品
  • 从“AI炼金术”到“研发加速器”:一个研发团队的趟坑与重生实录
  • B站 XMCVE Pwn入门课程学习笔记(9)
  • 【数学建模学习笔记】机器学习回归:XGBoost回归
  • 本地部署开源数据生成器项目实战指南
  • Agentic AI 架构全解析:到底什么是Agentic AI?它是如何工作的
  • AI助力软件UI概念设计:卓伊凡收到的客户设计图引发的思考
  • 零样本学习与少样本学习
  • QT6(事件与信号及事件过滤器)