主题
C++新特性
右值引用
- 左值和右值的区别在于能否取地址。左值能通过
&获取地址,而右值不能。 - 左值可以出现在
=的左边或右边,但右值只能出现在=的右边。 - 左值包括变量、数组元素、结构体成员等等。右值包括字面常量、诸如x+y等表达式、返回值不是引用的函数等。
右值引用可关联到右值,即可出现在赋值表达式右边但不能对其应用地址运算符的值。
c++
int a = 10; // a是左值,10是右值。
int b = a;
int&& rr1 = 100; // rr1是右值引用
int&& rr2 = a + b; // rr2是右值引用引入右值引用的主要目的之一是实现移动语义。
移动语义
Lambda
表达形式:[]() mutable -> returntype {};
| 语法 | 描述 |
|---|---|
| [] | 捕获列表:捕获外部变量供函数体使用。不可省略。[=]按值传递方式捕获函数体内使用的外部变量;[&]按引用传递方式捕获函数体内使用的外部变量。 |
| () | 参数列表:lambda函数的参数。 |
| mutable | 修饰符:允许在函数体中修改捕获的变量。 因为引用传递方式就可修改外部变量,所以基本不用。 |
| -> returntype | 返回类型:lambda函数的返回类型。 |
| {} | 函数体:lambda的函数体。 不可省略 |
- 如果使用了
mutable或-> returntype,则不能省略(),即使没有参数。 - 按值传递捕获的变量不受外部修改的影响,函数体内还是原值;按引用传递捕获的变量外部修改会影响函数体内的变量的值。
- 虽然返回值类型编译器可以推导,但最好还是注明返回值类型。
c++
auto l2 = [] {}; // 最简形式
auto add2 = [](int x, int y) -> int {return x + y;};
cout << add2(1, 2) << endl;
auto a(10), b(20);
auto add3 = [=, &b](int z) -> int {return a + b + z;};
a = 20;
b = 5;
cout << add3(10) << endl; // 25智能指针
| 智能指针类型 | 描述 |
|---|---|
| auto_ptr | C++98定义的智能指针模板,C++11后被淘汰了。 |
| unique_ptr | 独占所有权的智能指针,同一内存只能有一个 unique_ptr 指针。 |
| shared_ptr | 共享所有权的智能指针,多个 shared_ptr 可以指向同一内存。 采用引用计数,当复制或拷贝时,计数+1;当析构时计数-1,计数为0则释放内存。 |
| weak_ptr | 弱引用智能指针,用于与 shared_ptr 配合使用,避免循环引用导致的内存泄漏。 |
初始化
注:xxx_ptr 表示 unique_ptr 或 shared_ptr。
c++
// 初始化一个可以指向T类型的空的智能指针
xxx_ptr<T> ptr1;
// 初始化一个指向T类型实例的智能指针
xxx_ptr<T> ptr2(new T());
// 初始化一个可以指向T类型数组的空的智能指针
xxx_ptr<T[]> ptr3;
// 初始化一个指向T类型数组实例的智能指针
xxx_ptr<T[]> ptr4(new T[]);
// 初始化一个可以指向T类型实例的智能指针,并使用自定义删除器来释放内存
unique_ptr<T, D> ptr5;
// 虽然编译器不会报错,但ptr6无法通过赋值或reset接管对象(运行时异常退出)。
// 要么直接用ptr8的方式,要么就用ptr1的方式并使用ptr1.reset(new T(), D());
shared_ptr<T> ptr6(nullptr, D());
// 初始化一个指向T类型实例的智能指针,并使用自定义删除器来释放内存
unique_ptr<T, D> ptr7(new T());
shared_ptr<T> ptr8(new T(), D());
// make_unique(或 make_shared)会同时分配控制块(用于引用计数)和对象本身,
// 相比于 new 方式初始化,少一次内存分配次数,更加简洁高效(推荐)
unique_ptr<T> ptr9 = make_unique<T>(args);
shared_ptr<T> ptr10 = make_shared<T>(args);unique_ptr
| API | 描述 |
|---|---|
| get() | 获取接管对象的指针。 |
| reset(...args) | 销毁当前对象(如果存在),并接管新的对象(如果提供)或重置为空指针。 |
| release() | 仅释放接管对象的控制权,不会销毁对象。 |
- 不支持赋值和复制操作,需要使用
std::move()进行移动操作。
shared_ptr
| API | 描述 |
|---|---|
| get() | 获取接管对象的指针。 |
| reset(...args) | 当前对象引用计数-1(如果存在),并接管新的对象(如果提供,新对象引用计数+1)或重置为空指针。可指定自定义deleter |
| use_count() | 获取当前接管对象的引用计数。空指针调用返回为0 |
| swap() | 交换接管的对象,原对象的引用计数不变 |
weak_ptr
std::weak_ptr不能直接构造,必须从一个shared_ptr或另一个weak_ptr构造。它的构造和析构不会引起引用计数的增加或减少。
| API | 描述 |
|---|---|
| lock() | 返回接管对象的shared_ptr(引用计数+1),如果接管对象已被释放,则返回空的shared_ptr |
| reset() | 重置为空指针,注意引用计数不变 |
| use_count() | 获取当前接管对象的shared_ptr指针的引用计数。 |
| swap() | 交换接管的对象,原对象的引用计数不变 |
| expired() | 判断当前weak_ptr是否还有托管的对象,有则返回false,无则返回true |
- 不支持
*和->对指针的访问
Example:
c++
#include <iostream>
#include <memory>
using namespace std;
class Person
{
private:
std::string _name;
public:
Person() {
std::cout << "调用Person的构造函数" << std::endl;
_name = "Unknown";
}
Person(string name) {
std::cout << "调用Person的带参构造函数: " << name << std::endl;
_name = name;
}
~Person() { std::cout << "调用Person的析构函数" << std::endl; }
void doSomething() const { std::cout << _name << " doSomething...\n"; }
};
class PersonDeleter
{
public:
void operator()(Person* pt) {
pt->doSomething();
delete pt;
}
};
class Girl;
class Boy
{
private:
weak_ptr<Girl> _girlFriend;
public:
Boy()
{
cout << "Boy构造函数\n";
}
virtual ~Boy()
{
cout << "~Boy析构函数\n";
}
void setGirlFriend(shared_ptr<Girl> girlFriend)
{
cout << "In setGirlFriend a: " << girlFriend.use_count() << endl; // 2
// 赋值给weak_ptr不会让引用计数+1
// 这里是因为girlFriend作为值传递方式传参而使计数+1
// 当函数执行完之后,girlFriend会销毁使计数-1
_girlFriend = girlFriend;
cout << "In setGirlFriend b: " << _girlFriend.use_count() << endl; // 2
// 获取共享指针
shared_ptr<Girl> spGril =_girlFriend.lock();
cout << "In setGirlFriend c: " << spGril.use_count() << endl; // 3
spGril = nullptr;
cout << "In setGirlFriend d: " << _girlFriend.use_count() << endl; // 2
}
void resetGirlFriend()
{
if (!_girlFriend.expired())
{
_girlFriend.reset();
cout << "分手成功!\n";
}
else
cout << "已经没有女朋友了!\n";
}
};
class Girl {
private:
shared_ptr<Boy> _boyFriend;
public:
Girl() {
cout << "Girl构造函数" << endl;
}
virtual ~Girl() {
cout << "~Girl析构函数" << endl;
}
void setBoyFriend(shared_ptr<Boy> boyFriend) {
_boyFriend = boyFriend;
}
};c++
#include "smart_ptr.hpp"
void test_unique_ptr()
{
std::cout << "===================Test unique_ptr==========================\n";
std::unique_ptr<string> ptr_string(new string("zhangsan"));
std::unique_ptr<int> ptr_int(new int(100));
{
Person* person = new Person();
std::unique_ptr<Person> p2;
p2.reset(person); // 接管person实例的内存空间
std::unique_ptr<Person> p3;
p3 = std::move(p2); // 使用move把左值变为右值后就可以赋值了,p2被置为空
p3.reset(); // 等同于 p3 = nullptr;
// auto p = p3.release(); // 仅释放托管对象的控制权,不销毁对象。返回对象指针
// delete p; // 如果使用release(),则需要手动释放内存
}
std::cout << "\n*****测试自定义的 deleter ***** \n";
{
Person* person2 = new Person("Tom");
std::unique_ptr<Person, PersonDeleter> p4(person2);
}
}c++
#include "smart_ptr.hpp"
void test_shared_ptr()
{
std::cout << "===================Test shared_ptr==========================\n";
std::shared_ptr<int> ptr_int = make_shared<int>(100);
{
std::shared_ptr<Person> p1 = make_shared<Person>("Tom");
std::shared_ptr<Person> p2;
std::cout << "p2.use_count() = " << p2.use_count() << std::endl;
p2 = p1;
std::cout << "count: " << p1.use_count() << " " << p2.use_count() << std::endl;
// 2 2
p2.reset(); // 当前接管对象的引用计数-1,并将p2置为空指针。等同于 p2 = nullptr;
std::cout << "count: " << p1.use_count() << " " << p2.use_count() << std::endl;
// 1 0
}
{
Person* person = new Person("Lucy");
std::shared_ptr<Person> p3(person, PersonDeleter());
std::shared_ptr<Person> p4(p3); // 等同于 p4 = p3;
std::cout << "count: " << p3.use_count() << " " << p4.use_count() << std::endl;
p4 = nullptr; // p4没有使用自定义deleter
std::cout << "count: " << p3.use_count() << " " << p4.use_count() << std::endl;
}
{
Person* person = new Person("Lucy2");
// 用下面的方式初始化p5,在运行p5.reset时会异常退出
//std::shared_ptr<Person> p5(nullptr, PersonDeleter());
std::shared_ptr<Person> p5;
p5.reset(person, PersonDeleter());
std::cout << "p5 count: " << p5.use_count() << std::endl;
p5.reset(); // 使用自定义deleter
std::cout << "p5 count: " << p5.use_count() << std::endl;
}
}c++
#include "smart_ptr.hpp"
void test_weak_ptr()
{
cout << "===================Test weak_ptr==========================\n";
shared_ptr<Boy> spBoy(new Boy());
shared_ptr<Girl> spGirl(new Girl());
spGirl->setBoyFriend(spBoy);
cout << "Before setGirlFriend: " << spGirl.use_count() << endl; // 1
spBoy->setGirlFriend(spGirl);
cout << "After setGirlFriend: " << spGirl.use_count() << endl; // 1
spBoy->resetGirlFriend();
cout << spGirl.use_count() << endl; // 1
}使用指南
- 如果程序需要使用多个指向同一个对象的指针,应该使用
shared_ptr;如果有循环引用的情况,则使用weak_ptr。 unique_ptr可以转化为shared_ptr,但反之则不行。- 不要把原生对象的指针托管给多个智能指针,这非常危险!
c++
int* x = new int(10);
shared_ptr<int> p1(x); // 或使用p1.reset(x)的形式;
shared_ptr<int> p2(x);
p1 = nullptr; // p1所接管的指针所指向的内存空间会被释放。
// p2 所接管的指针将变成悬挂指针。- 如果使用了release(),记得手动释放返回的指针。
- 不要delete智能指针get()返回的指针。既然用上了智能指针,那就让代码“智能”一点。
- 智能指针作为函数参数或返回值的情况
函数 unique_ptr shared_ptr 返回值 所有权转让给接收返回值的unique_ptr 复制行为,引用计数+1,但函数执行完后会自动销毁,引用计数-1 参数:值传递 所有权转让给函数的局部变量参数,函数执行完后会销毁(谨慎) 同上 参数:引用传递 所有权仍归函数外的实参,函数执行完后不会销毁 计数不变
c++
static unique_ptr<Person> func1(string name)
{
return unique_ptr<Person>(new Person(name));
}
static void func2(unique_ptr<Person> up)
{
up->doSomething();
}
static void func3(unique_ptr<Person>& up)
{
up->doSomething();
}
void test_func_unique_ptr()
{
auto p5 = func1("P5");
func3(p5);
Person* person3 = new Person("P6");
unique_ptr<Person> p6(person3);
func2(std::move(p6)); // 函数执行完后,person3的内存会被释放
cout << "After func2\n";
}运行结果如下: 