首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【C++篇】C++11新特性总结2

【C++篇】C++11新特性总结2

作者头像
用户11719958
发布2025-12-30 15:00:31
发布2025-12-30 15:00:31
1290
举报

1,可变参数模板

1.1,基本语法及原理

  • C++11支持可变参数模板,也就是说支持可变数量参数的函数模板和类模板。可变数目的参数被称为参数包,存在两种参数包:模板参数包,表示零个或多个模板参数,函数参数包:表示零个或多个函数参数。

template <class...Args> void Func(Args...args) {} template <class...Args> void Func(Args&...args) {} template <class...Args> void Func(Args&&...args) {}

  • 我们用省略号来指出一个模板参数或函数参数的表示一个包, 在模板参数列表中,class...或 typename...指出接下来的参数表示零或多个类型列表;在函数参数列表中,类型名后面跟...指出 接下来表示零或多个形参对象列表;函数参数包可以用左值引用或右值引用表示,跟前面普通模板⼀样,每个参数实例化时遵循引用折叠规则。
  • 可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。

1.2,包扩展

对于⼀个参数包,我们除了能计算他的参数个数,我们能做的唯一的事情就是扩展它,当扩展⼀个 包时,我们还要提供用于每个扩展元素的模式,扩展⼀个包就是将它分解为构成的元素,对每个元素应用模式,获得扩展后的列表。我们通过在模式的右边放⼀个省略号(...)来触发扩展操作。

void showlist() { cout << endl; } template <class T,class...Args> void showlist(T x, Args...args) { cout << x << " "; showlist(args...); } template <class...Args> void print(Args...args) { //args是N个参数的参数包 //调用showlist,参数包的第一个传给x //剩下N-1个传给第二个参数 showlist(args...); } int main() { print(1, string("xxxxxx"), 2.2); return 0; }

4.3,emplace系列接口

template <class...Args> void emplace_back (Args&&... args);

  • C++11以后STL容器新增了empalce系列的接口,empalce系列的接口均为模板可变参数,功能上 兼容push和insert系列,,empalce还支持直接插入构造T对象的参数,这样有些场景会更高效一些,可以直接在容器空间上构造T对象。
  • emplace_back接口比insert和push_back接口更高效。

2,新的类功能

2.1,默认的移动构造和移动赋值

  • 原来C++类中,有6个默认成员函数:构造函数/析构函数/拷贝构造函数/拷贝赋值重载/取地址重 载/const 取地址重载,最后重要的是前4个,后两个⽤处不大,默认成员函数就是我们不写编译器 会⽣成⼀个默认的。C++11新增了两个默认成员函数,移动构造函数和移动赋值运算符重载,这两个函数在上篇文章中讲过。
  • 如果你没有自己实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
  • 默认移动赋值与上面的移动构造完全类似。
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

class Person { public: Person(const char* name="张三", int age=1) :_name(name) ,_age(age) {} private: string _name; int _age; }; int main() { Person s1; Person s2 = s1; Person s3 = move(s1); return 0; }

s2调用默认拷贝构造。s3调用移动构造函数,对于string类型,内部实现了移动构造,就嗲用地洞构造。对于int内置类型,逐字节拷贝。

2.2,default和delete

default关键字:

作用: 显式要求编译器生成某个特殊成员函数的默认实现。 适用于:默认构造函数、析构函数、拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符。

使用场景: 当类中定义了其他构造函数时,编译器可能不再自动生成默认构造函数。用default显式恢复:

class MyClass { public: MyClass(int x) {} // 自定义构造函数 MyClass() = default; // 显式生成默认构造函数 };

delete关键字

作用 显式禁用某个函数(包括成员函数和普通函数),阻止其被调用。

使用场景:

1,禁止拷贝语义 禁用拷贝构造函数和拷贝赋值运算符,常见于资源管理类(如独占资源):

class NonCopyable { public: NonCopyable() = default; NonCopyable(const NonCopyable&) = delete; // 禁用拷贝构造 NonCopyable& operator=(const NonCopyable&) = delete; // 禁用拷贝赋值 };

2禁用隐式类型转换 删除特定参数类型的重载函数,避免意外的隐式转换:

class MyClass { public: void Process(int x) {} void Process(double) = delete; // 禁止double参数调用 }; MyClass obj; obj.Process(10); // OK obj.Process(3.14); // 编译错误:函数已删除

3,禁用不希望的函数 例如,禁止通过某些方式构造对象:

class Singleton { public: Singleton() = default; Singleton(const Singleton&) = delete; // 禁止拷贝 };

对比传统的实现方式:

1,default vs 隐式生成

  • 显式声明意图更清晰,避免因代码修改导致默认函数被隐式删除。

2,delete vs 私有化函数(C++11前)

  • 旧方法:将函数声明为private且不实现,但错误在链接阶段才暴露。
  • C++11的delete在编译阶段直接报错,提供更清晰的错误信息。

2.3,final与override

final的作用:

  • 修饰类:防止类被继承。

class FinalClass final { // 类的内容 };

  • 修饰成员函数:防止成员函数在派生类中被覆盖。

class Base { public: virtual void show() final { std::cout << "Base show" << std::endl; } }; class Derived : public Base { public: void show() override { // 错误:Base::show是final的 std::cout << "Derived show" << std::endl; } };

override是一个成员函数的修饰符,用于显式地表示一个成员函数覆盖了基类中的虚函数。它主要用于方法重写。

class Base { public: virtual void show() { std::cout << "Base show" << std::endl; } }; class Derived : public Base { public: void show() override { // 显式声明覆盖 std::cout << "Derived show" << std::endl; } };

作用

  • 显式声明覆盖:当派生类中的成员函数覆盖了基类中的虚函数时,使用override可以显式地声明这种覆盖关系,增强代码的可读性和安全性。
  • 编译器检查:如果使用了override,但实际并没有覆盖基类中的虚函数(例如,方法签名不匹配),编译器会报错。

3,STL容器的一些变化

unordered_map和unordered_set的加入。右值引用和移动语义相关的push/insert/emplace系列 接口和移动构造和移动赋值,还有initializer_list版本的构造 等。

容器的范围for遍历

std::vector<int> vec = {1, 2, 3}; for (auto& num : vec) { std::cout << num << " "; }

4,lambda

4.1,lambda表达式的语法

  • lambda 表达式本质是⼀个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。lambda 表达式语法使用层而言没有类型,所以我们一般是用auto或者模板参数定义的对象去接受lambda对象。
  • lambda表达式的格式: [capture-list] (parameters)-> return type {function body}
  • [capture-list] :捕捉列表,该列表总是出现在lambda 函数的开始位置 ,编译器根据 [] 来判断接下来的代码是否为lambda函数。捕捉列表能够捕捉上下文中的变量供 lambda 函数使用,捕捉列表可以传值和传引用捕捉。捕捉列表为空也不能省略。
  • (parameters):参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以省略()。
  • -> return type ::返回值类型,⽤追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。⼀般返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  • function body::函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

示例:

auto add1 = [](int x, int y)->int {return x + y; }; auto func1 = [] { cout << "hello world" << endl; return 0; }; auto swap1 = [](int& x, int& y) { int tmp = x; x = y; y = tmp; }; //sort的第三个参数可以传lambda表达式的比较函数 std::sort(vec.begin(), vec.end(), [](int a, int b) { return a > b; // Sort in descending order });

4.2,捕捉列表

  • lambda 表达式中默认只能用 lambda 函数体和参数中的变量,如果想用 外层作用域中的变量需要进行捕捉。
  • 第⼀种捕捉方式是在捕捉列表中显示的传值捕捉和传引用捕捉,捕捉的多个变量用逗号分割。[x,y,&z]表示x和y值捕捉,z引用捕捉。

int a = 0, b = 1, c = 2, d = 3; //显示传值和传引用捕捉 auto func1 = [a, &b] { //值捕捉的变量不可以修改 //引用捕捉的变量可以修改 //a++; 报错 b++; int ret = a + b; return ret; }; cout << func1() << endl; //传值捕捉本质是一种拷贝,并且被const修饰了,不能修改 //mutable相当于去掉了const属性,可以修改了 //但是内部修改不会影响外部的值,因为是一种拷贝 auto func = [=]()mutable { a++; b++; c++; d++; return a + b + c + d; }; cout << func() << endl;

  • 第二种捕捉方式是在捕捉列表中隐式捕捉,我们在捕捉列表写一个 = 表示隐式值捕捉,在捕捉列表 写一个 & 表示隐式引用捕捉,这样我们 lambda 表达式中用了那些变量,编译器就会自动捕捉那些变量。

//隐式值捕捉 //用了那些变量就捕捉那些变量 auto func2 = [=] { int ret = a + b + c; return ret; }; cout << func2() << endl; //隐式引用捕捉 //用了那些变量就捕捉那些变量 auto func3 = [&] { a++; b++; c++; }; func3(); cout << a << " " << b << " " << c << " " << endl;

  • 第三种捕捉方式是在捕捉列表中混合使用隐式捕捉和显示捕捉。[=,&x]表示其他变量隐式值捕捉, x引用捕捉;[&,x,y]表示其他变量引用捕捉,x和y值捕捉。当使用混合捕捉时,第⼀个元素必须是 &或=,并且&混合捕捉时,后⾯的捕捉变量必须是值捕捉,同理=混合捕捉时,后⾯的捕捉变量必须是引用捕捉。

//混合捕捉 auto func4 = [&, a, b] { //a++ 报错 //b++ 报错 c++; d++; return a + b + c + d; }; func4(); cout << a << " " << b << " " << c << " " << endl; //混合捕捉 auto func5 = [=, &a, &b] { a++; b++; //c++ 报错 //d++ 报错 }; func5();

  • lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态 局部变量和全局变量,静态局部变量和全局变量也不需要捕捉。lambda 表达式中可以直接使用。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空

//局部的静态和全局变量不能捕捉,不需要捕捉就可以用 static int m = 1; static int n = 2; auto func6 = [ ] { return m + n; };

5,包装器

5.1,function

  • std::function 是⼀个类模板,也是⼀个包装器。std::function 的实例对象可以包装存储其他的可以调用对象,包括函数指针、仿函数、 lambda 、bind 表达式等,存储的可调用对象被称为std::function的目标。若std::function不含目标,则成称它为空。调用空std::function的目标导致抛出std::bad_function_call异常。
  • 它被定义在<functiinal>头文件中 。
  • 函数指针,lambda,仿函数等可调用对象的类型各不相同。std::function的又是就是统一类型,对他们进行包装。

#include <functional> #include <iostream> int add(int a, int b) { return a + b; } struct Functor { int operator()(int a, int b) { return a + b; } }; int main() { std::function<int(int, int)> f1 = add; // 包装普通函数 std::function<int(int, int)> f2 = Functor(); // 包装仿函数 std::function<int(int, int)> f3 = [](int a, int b) { return a + b; }; // 包装Lambda表达式 std::cout << f1(1, 2) << std::endl; // 输出3 std::cout << f2(1, 2) << std::endl; // 输出3 std::cout << f3(1, 2) << std::endl; // 输出3 }

包装成员函数

class Plus { public: Plus(int n=1) :_n(n) {} static int plusi(int a, int b) { return a + b; } double plusd(double x, double y) { return (x + y) * 10; } private: int _n; }; int main() { //包装静态成员函数 需要指明类域+& function<int(int, int)> f1 = &Plus::plusi; //包装非静态成员函数 需要指明类域+& //非静态成员函数包含this指针 function<double(Plus*, double, double)> f2 = &Plus::plusd; Plus pl; cout << f2(&pl, 1.1, 2.2) << endl; //下面的写法也可以 function<double(Plus, double, double)> f3 = &Plus::plusd; function<double(Plus&, double, double)> f4 = &Plus::plusd; return 0; }

5.2,bind

  • bind 是⼀个函数模板, 对接受的可调用的对象 进行处理后返回一个可调用对象。bind 可以用来调整参数个数和参数顺序。bind 也在<function>这个头文件中。
  • 调⽤bind的一般形式: auto newCallable = bind(callable,arg_list); 其中 newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的 参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
  • arg_list中的参数可能包含形如_n的名字,其中n是⼀个整数,这些参数是占位符,表示 newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表示生成的可调⽤对象 中参数的位置:_1为newCallable的第⼀个参数,_2为第⼆个参数,以此类推。_1/_2/_3....这些占位符在placeholders的一个命名空间中。

总结:std::bind 是一个绑定器,用于将函数或可调用对象的参数绑定到特定的值上,从而创建一个新的可调用对象。它通常用于固定某些参数,使函数或可调用对象的调用更简单。

调整参数顺序示例:

using placeholders::_1; using placeholders::_2; using placeholders::_3; int sub(int a, int b) { return (a - b) * 10; } int main() { auto sub1 = bind(sub, _1, _2); cout << sub1(10, 5) << endl; auto sub2 = bind(sub, _2, _1); cout << sub2(10, 5) << endl; return 0; }

调整参数个数示例:

int subx(int a, int b, int c) { return (a - b - c) * 10; } //绑死一些参数,达到修改参数个数的目的 //分别绑死第1,2,3个参数 auto sub3 = bind(subx, 100, _1, _2); //a被绑死=100 cout << sub3(5, 1) << endl; auto sub4 = bind(subx, _1, 100, _2); cout << sub4(5, 1) << endl; auto sub5 = bind(subx, _1, _2,100); cout << sub5(5, 1) << endl;

在function部分,在包装一个类的非静态成员函数时,参数必须要传一个对象或者对象的地址,我们可以使用bind来绑定这个参数。

using placeholders::_1; using placeholders::_2; using placeholders::_3; class Plus { public: Plus(int n=1) :_n(n) {} static int plusi(int a, int b) { return a + b; } double plusd(double x, double y) { return (x + y) * 10; } private: int _n; }; int main() { //包装静态成员函数 需要指明类域+& function<int(int, int)> f1 = Plus::plusi; //包装非静态成员函数 需要指明类域+& //非静态成员函数包含this指针 function<double(Plus*, double, double)> f2 = &Plus::plusd; Plus pl; cout << f2(&pl, 1.1, 2.2) << endl; //绑定Plus()参数,传参时就不需要传了 function<double(double, double)> f3 = bind(&Plus::plusd, Plus(), _1, _2); cout << f3(1.1, 2.2) << endl; return 0; }

C++11中的一个重要内容:智能指针,在一篇中更新。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1,可变参数模板
    • 1.1,基本语法及原理
    • 1.2,包扩展
    • 4.3,emplace系列接口
  • 2,新的类功能
    • 2.1,默认的移动构造和移动赋值
    • 2.2,default和delete
    • 2.3,final与override
  • 3,STL容器的一些变化
  • 4,lambda
    • 4.1,lambda表达式的语法
    • 4.2,捕捉列表
  • 5,包装器
    • 5.1,function
    • 5.2,bind
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档