1.RAll
RAII:用于有效地管理资源的获取和释放。
基本思想:资源的获取应当在对象的构造函数中进行,而资源的释放则应当在对象的析构函数中进行。
RAII 的主要优势:
- RAII 可以确保资源的正确获取和释放,避免了手动管理资源时可能发生的错误。
- 当使用 RAII 时,如果在构造函数中发生异常,对象会在析构函数中自动被销毁,从而保证资源被正确释放。
2.智能指针
2.1 普通指针存在的问题
内存泄漏: 使用普通指针时,需要手动分配和释放内存,这就需要确保在适当的时候调用 delete
或 delete[]
来释放动态分配的内存,否则会导致会导致内存泄漏。
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <iostream>
int main() { int* ptr = new int;
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;
std::cout << *ptr << std::endl;
return 0; }
|
2.2 std::shared_ptr
std::shared_ptr:
共享式智能指针,允许多个指针共享对同一对象的所有权,通过引用计数机制来管理资源的生命周期。
创建和初始化:
- 方法一:使用
std::make_shared
(好)
1 2
| std::shared_ptr<int> sharedPtr1 = std::make_shared<int>(42)
|
1 2
| 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() { std::shared_ptr<MyClass> sharedPtr1 = std::make_shared<MyClass>(); std::shared_ptr<MyClass> sharedPtr2 = sharedPtr1;
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;
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; bPtr->aPtr = aPtr;
return 0; }
|
解决循环引用的方法:
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; };
int main() { std::shared_ptr<ClassA> aPtr = std::make_shared<ClassA>(); std::shared_ptr<ClassB> bPtr = std::make_shared<ClassB>();
aPtr->bPtr = bPtr; bPtr->aWeakPtr = aPtr;
return 0; }
|
获取 std::shared_ptr
:用 std::weak_ptr
的 lock
成员函数来获取一个指向共享对象的 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);
std::shared_ptr<int> sharedPtrCopy = weakPtr.lock();
if (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
转移所有权时,它所管理的对象将被销毁。
1 2 3 4 5 6 7 8 9 10 11
| #include <memory>
int main() { std::unique_ptr<int> uniquePtr1 = std::make_unique<int>(42);
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_ptr
,std::unique_ptr
是一种更轻量级的智能指针,因为它不需要维护引用计数。