1.RAll

RAII:用于有效地管理资源的获取和释放。

基本思想:资源的获取应当在对象的构造函数中进行,而资源的释放则应当在对象的析构函数中进行。

RAII 的主要优势:

  • RAII 可以确保资源的正确获取和释放,避免了手动管理资源时可能发生的错误。
  • 当使用 RAII 时,如果在构造函数中发生异常,对象会在析构函数中自动被销毁,从而保证资源被正确释放。

2.智能指针

2.1 普通指针存在的问题

内存泄漏: 使用普通指针时,需要手动分配和释放内存,这就需要确保在适当的时候调用 deletedelete[] 来释放动态分配的内存,否则会导致会导致内存泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>

int main() {
// 动态分配一个整数的内存
int* ptr = new int;

// 其他代码...

// 忘记释放内存
// delete ptr; // 此行代码注释掉了,导致内存泄漏

return 0;
}

注:普通指针不会自动调用析构函数。

悬挂指针:程序中的某个部分释放了一块动态分配的内存,而其他部分仍然持有指向该内存的指针,并尝试使用或修改这个指针所指向的内存时,就会导致悬挂指针问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>

int main() {
int* ptr = new int; // 分配动态内存

delete ptr; // 释放内存

// ptr 现在是悬挂指针,指向已释放的内存
// 下面的访问操作是未定义行为
std::cout << *ptr << std::endl;

return 0;
}

2.2 std::shared_ptr

std::shared_ptr:共享式智能指针,允许多个指针共享对同一对象的所有权,通过引用计数机制来管理资源的生命周期。

创建和初始化:

  • 方法一:使用std::make_shared(好)
1
2
// 使用 std::make_shared 创建 shared_ptr
std::shared_ptr<int> sharedPtr1 = std::make_shared<int>(42)
  • 方法二:使用构造函数
1
2
// 使用构造函数创建 shared_ptr
std::shared_ptr<int> sharedPtr2(new int(42));

共享所有权:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <memory>

class MyClass {
public:
MyClass() {
std::cout << "MyClass constructed." << std::endl;
}

~MyClass() {
std::cout << "MyClass destructed." << std::endl;
}
};

int main() {
// 创建 shared_ptr 共享同一个对象
std::shared_ptr<MyClass> sharedPtr1 = std::make_shared<MyClass>();
std::shared_ptr<MyClass> sharedPtr2 = sharedPtr1;

// 当最后一个 shared_ptr 离开作用域时,对象的析构函数会被调用
return 0;
}

引用计数:使用计数器,记录当前有多少个指针(引用)指向该资源。当计数器为零时,表示没有任何指针指向该资源,资源可以被释放。

注:std::shared_ptr 会为每个共享的对象分配一个控制块,这个控制块包含引用计数、指向实际对象的指针等信息。

1
2
3
4
5
6
7
8
9
10
11
#include <memory>
#include <iostream>

int main() {
std::shared_ptr<int> sharedPtr1 = std::make_shared<int>(42);
std::shared_ptr<int> sharedPtr2 = sharedPtr1;

std::cout << "use_count: " << sharedPtr1.use_count() << std::endl; // 输出 2

return 0;
}

std::shared_ptr的问题:循环引用

循环引用是指两个或多个对象相互引用,形成一个环状结构,导致它们的引用计数永远不会降为零。这种情况可能导致内存泄漏,因为对象的资源(如动态分配的内存)将无法被释放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <memory>
#include <iostream>

class ClassB; // 前向声明

class ClassA {
public:
std::shared_ptr<ClassB> bPtr;
};

class ClassB {
public:
std::shared_ptr<ClassA> aPtr;
};

int main() {
// 创建对象并建立循环引用
std::shared_ptr<ClassA> aPtr = std::make_shared<ClassA>();
std::shared_ptr<ClassB> bPtr = std::make_shared<ClassB>();

aPtr->bPtr = bPtr; // ClassA 包含 ClassB
bPtr->aPtr = aPtr; // ClassB 包含 ClassA

// 对象的引用计数永远不会降为零,导致内存泄漏

return 0;
}

解决循环引用的方法:

  • 使用 std::weak_ptr 打破循环引用

2.3 std::weak_ptr

std::weak_ptr:用于解决循环引用和避免 std::shared_ptr 的引用计数增加导致的内存泄漏问题,通常用于与 std::shared_ptr 共同工作

std::weak_ptr 不会增加对象的引用计数,因此它不会影响对象的生命周期。

解决循环引用问题:当两个对象相互持有对方的 std::shared_ptr 时,其中一个或两个需要使用 std::weak_ptr,以避免形成循环引用,从而防止内存泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <memory>

class ClassB;

class ClassA {
public:
std::shared_ptr<ClassB> bPtr;
};

class ClassB {
public:
std::weak_ptr<ClassA> aWeakPtr; // 使用 std::weak_ptr 避免循环引用
};

int main() {
// 创建 shared_ptr1 和 shared_ptr2
std::shared_ptr<ClassA> aPtr = std::make_shared<ClassA>();
std::shared_ptr<ClassB> bPtr = std::make_shared<ClassB>();

// 将 shared_ptr1 和 shared_ptr2 关联起来
aPtr->bPtr = bPtr;
bPtr->aWeakPtr = aPtr;

// ...

return 0;
}

获取 std::shared_ptrstd::weak_ptrlock 成员函数来获取一个指向共享对象的 std::shared_ptr

1
2
3
4
5
6
7
8
9
10
11
12
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
std::weak_ptr<int> weakPtr(sharedPtr);

// 获取 shared_ptr
std::shared_ptr<int> sharedPtrCopy = weakPtr.lock();

if (sharedPtrCopy) {
// 共享对象存在
// 使用 sharedPtrCopy...
} else {
// 共享对象已销毁
}

判断对象是否存在: 可以使用 expired 成员函数检查 std::weak_ptr 引用的对象是否已经被销毁。

1
2
3
4
5
6
7
8
9
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
std::weak_ptr<int> weakPtr(sharedPtr);

if (!weakPtr.expired()) {
// 共享对象存在
// ...
} else {
// 共享对象已销毁
}

2.4 std::unique_ptr

std::unique_ptr:std::shared_ptr 不同,std::unique_ptr 具有“独占”的所有权语义,即同一时刻只能有一个 std::unique_ptr 指向一个特定的对象。当 std::unique_ptr 被销毁或通过 std::move 转移所有权时,它所管理的对象将被销毁。

  • 创建 std::unique_ptr
1
2
3
4
5
6
7
8
9
10
11
#include <memory>

int main() {
// 使用 std::make_unique 创建 std::unique_ptr
std::unique_ptr<int> uniquePtr1 = std::make_unique<int>(42);

// 使用构造函数创建 std::unique_ptr
std::unique_ptr<int> uniquePtr2(new int(42));

return 0;
}
  • 移动所有权
1
2
3
4
5
6
7
8
9
10
#include <memory>

int main() {
std::unique_ptr<int> uniquePtr1 = std::make_unique<int>(42);

// 移动所有权
std::unique_ptr<int> uniquePtr2 = std::move(uniquePtr1);

return 0;
}

注:相对于 std::shared_ptrstd::unique_ptr 是一种更轻量级的智能指针,因为它不需要维护引用计数。