【C++】C++11介绍(Ⅱ)
目录
1.可变模版参数
1.1 基本语法
编辑
1.2 包扩展
2. lambda
2.1 基本语法
2.2 应用场景
3.包装器
3.1 function
3.2 bind
4. 新的类功能
4.1 默认移动构造和移动赋值
4.2 delete和default
5. 智能指针
5.1 unique_ptr
5.3 weak_ptr
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) {}
这里的Args是模板参数包,args是函数参数包,可以包含零个或多个参数。
我们可以用sizeof...来计算参数包中的参数个数
template <class ...Args>
void Print(Args&&... args)
{cout << sizeof...(args) << endl;
}int main()
{double x = 2.2;Print(); // 0个参数 Print(1); // 1个参数 Print(1, string("aaaaa")); // 2个参数 Print(1.1, string("bbbbbb"), x); //3个参数 return 0;
}
1.2 包扩展
参数包本身不能直接使用,需要通过包扩展来展开。常见的扩展方法就是递归推导
2. lambda
Lambda表达式是C++11引入的一种定义匿名函数对象的简洁方式。它允许我们在需要函数的地方直接内联定义函数,而无需单独声明命名函数。
2.1 基本语法
[ 捕获变量 ](参数)-> 返回类型 {函数体}
捕获列表有以下几种形式:
[] 不捕获任何变量
[=] 以值方式捕获所有外部变量
[&] 以引用方式捕获所有外部变量
[a, &b] 混合捕获方式
2.2 应用场景
Lambda表达式特别适用于:一次性使用的函数对象
// 在STL算法中使用lambda
std::vector<int> nums = {1, 2, 3, 4, 5};
std::for_each(nums.begin(), nums.end(), [](int n) {std::cout << n * 2 << " ";
});
3.包装器
3.1 function
std::function 是一个通用的函数包装器,可以存储、复制和调用任何可调用对象
function<返回类型(参数类型)> 包装器名称 = 被包装函数对象
#include <functional>std::function<int(int, int)> adder = [](int a, int b) { return a + b; };
int result = adder(2, 3); // 结果为5
3.2 bind
std::bind 用于部分应用函数,可以绑定参数、重排参数顺序
#include<iostream>
#include<functional>
using namespace std;using placeholders::_1;
using placeholders::_2;
using placeholders::_3;int Sub(int a, int b)
{return a-b;
}int main()
{// bind 本质返回的是一个仿函数对象 // 调整参数顺序 // _1代表第一个实参 // _2代表第二个实参 // ...auto sub1 = bind(Sub, _1, _2);cout << sub1(10, 5) << endl;//结果是5//调整参数顺序auto sub2 = bind(Sub, _2, _1);cout << sub2(10, 5) << endl;//结果是-5//绑定第一个参数是20auto sub3 = bind(Sub, 20, _1);cout << sub3(10, 5) << endl;//结果是10return 0;
}
4. 新的类功能
4.1 默认移动构造和移动赋值
前面我们已经学过了C++类中,有6个默认成员函数:构造函数/析构函数/拷贝构造函数/拷贝赋值重载/取地址重载/const取地址重载。我们用的最多的是前四个,这些函数都是我们在类中不写编译器会自动生成的。C++11新增了两个默认成员函数,就是移动构造函数和移动赋值重载。
如果你没有实现移动构造,且没有实现析构、拷贝构造、拷贝赋值中的任意一个。那么编译器会自动生成一个默认移动构造;
如果你没有实现移动赋值重载函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。
默认生成的移动构造/移动赋值重载,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。
如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
4.2 delete和default
函数声明 = default:显式要求编译器生成默认版本的函数
函数声明 = delete:禁止使用某些函数
5. 智能指针
C++98设计的一个智能指针叫 auto_ptr ,它的特点是在拷贝时把被拷贝对象的资源管理权交给拷贝对象,但这样可能会使被拷贝对象悬空,从而报错。这是一个不太好的智能指针,不建议使用。
C++11之后引入了三种智能指针,用于自动管理动态分配的内存:
5.1 unique_ptr
unique_ptr 正如它的名字所说,它的特点就是具有唯一性,不支持拷贝,只支持移动。
5.2 shared_ptr
shared_ptr 的特点是支持拷贝,也支持移动,共享资源的管理,使用引用计数方式实现
5.3 weak_ptr
weak_ptr 不支持RAII,也就是说它不能直接管理资源,其产生的本质是解决shared_ptr的循环引用导致内存泄漏的问题。
#include<iostream>
#include<memory>using namespace std;int main()
{auto_ptr<int> ap1(new int(3));auto_ptr<int> ap2(ap1);//此时ap1悬空unique_ptr<int> up1(new int(5));unique_ptr<int> up2(move(up1));//可以移动,但此时up1已经悬空,要注意//unique_ptr<int> up3(up1);//不支持拷贝,会报错shared_ptr<int> sp1(new int(10));shared_ptr<int> sp2(move(sp1));//支持移动,但sp1也悬空了shared_ptr<int> sp3(sp2);//也支持拷贝return 0;
}
这里我们可以看到被移动后的指针都悬空了,所以在移动时一定要小心!
//weak_ptr使用场景
#include <memory>
#include <iostream>class B; // 前向声明class A {
public:std::shared_ptr<B> b_ptr;~A() { std::cout << "A destroyed\n"; }
};class B {
public:std::shared_ptr<A> a_ptr; // 这里会造成循环引用!~B() { std::cout << "B destroyed\n"; }
};class C {
public:std::weak_ptr<A> a_ptr; // 使用 weak_ptr 打破循环引用~C() { std::cout << "C destroyed\n"; }
};void testCycle() {auto a = std::make_shared<A>();auto b = std::make_shared<B>();a->b_ptr = b;b->a_ptr = a; // 循环引用!内存泄漏!std::cout << "A use count: " << a.use_count() << std::endl; // 2std::cout << "B use count: " << b.use_count() << std::endl; // 2
} // a 和 b 的引用计数永远不为0,无法释放!void testNoCycle() {auto a = std::make_shared<A>();auto c = std::make_shared<C>();c->a_ptr = a; // weak_ptr 不增加引用计数std::cout << "A use count: " << a.use_count() << std::endl; // 1
} // 正常释放
int main()
{testCycle();testNoCycle();return 0;
}