Article directory
1. unique_ptr
std::unique_ptr
It is a smart pointer, which is mainly used to encapsulate raw pointers in C++ to ensure memory safety. When std::unique_ptr
the life cycle ends, it automatically deletes the object it owns.
1.1 Basic functions and features
-
Unique Ownership : True to its name "unique", this type of pointer holds unique ownership of the object it points to, meaning that at any point in time, only one
unique_ptr
can point to a given object. -
Prevent memory leaks : It automatically manages the life cycle and
unique_ptr
automatically deletes the object it points to when the life cycle ends (such as leaving the scope). This ensures that memory leaks will not be caused by forgetting to manually release memory. -
Non-copyable but movable :
unique_ptr
Is non-copyable, preventing multiple pointers from taking ownership of the same object. However, ownership can be ported, meaning ownership can beunique_ptr
transferred from one to another.
The following code demonstrates these features:
#include <memory>
// 定义一个函数,用于演示unique_ptr的功能
void Process(std::unique_ptr<int> ptr) {
// 在此函数中,ptr是唯一对内存的所有者
// 在此函数结束时,ptr将被销毁,并且其指向的内存也将被释放
}
int main() {
std::unique_ptr<int> ptr1(new int(5)); // 分配一个新的int
// std::unique_ptr<int> ptr2 = ptr1; // 编译错误,unique_ptr是不可复制的
std::unique_ptr<int> ptr3 = std::move(ptr1); // ptr1的所有权被移动到ptr3,现在ptr1不再拥有任何对象,ptr1变为nullptr
Process(std::move(ptr3)); // ptr3的所有权被移动到Process函数的参数,然后在函数结束时释放内存
return 0;
}
1.2 Managing dynamic arrays
unique_ptr
Can also be used to manage dynamic arrays, in which case when unique_ptr
the lifetime is over, will be called delete[]
to free the memory, rather than individually delete
.
int main() {
std::unique_ptr<int[]> arr(new int[10]); // arr指向一个包含10个int的新数组
for(int i = 0; i < 10; i++) {
arr[i] = i; // 我们可以像使用普通数组一样使用它
}
// 当arr离开作用域时,它会自动删除它所拥有的数组,无需手动调用 delete[] arr;
return 0;
}
1.3 MoveConstructible and MoveAssignable
unique_ptr
It is removable but not copyable. This means you can unique_ptr
transfer ownership of one to another unique_ptr
, but you can't create unique_ptr
a copy of one. This is because unique_ptr
the original design intention is to ensure that there is only one owner of the managed object at any time.
As shown in the following code:
#include <memory>
std::unique_ptr<int> p1(new int(42));
std::unique_ptr<int> p2 = std::move(p1); // Now p2 owns the int, and p1 is set to nullptr.
// This would be an error:
// std::unique_ptr<int> p3 = p2; // This is not allowed because unique_ptr is not copyable.
1.4 Transfer of ownership
As always, unique_ptr
objects cannot be copied, but ownership can be std::move()
transferred via . Transferring ownership also changes the original unique_ptr
object. When ownership unique_ptr
is transferred from one to another unique_ptr
, the original unique_ptr
will be set to null. This means that any attempt to use the original unique_ptr
will result in undefined behavior because it is now a null pointer.
std::unique_ptr<int> ptr1(new int(10));
std::unique_ptr<int> ptr2 = std::move(ptr1); // 所有权转移
// 此时,ptr1已经不再拥有任何对象,而ptr2拥有了该对象。
1.5 Custom deleter
unique_ptr
Provides the ability to use custom deleters, which can be useful in certain situations. For example, you can use a custom deleter when you need a specific cleaner or have specific memory management needs.
auto deleter = [](int* ptr) {
std::cout << "Custom deleting. \n";
delete ptr;
};
std::unique_ptr<int, decltype(deleter)> ptr(new int, deleter);
In the above code, when unique_ptr
destructed, the specified deleter will be called to release the memory.
1.6 Use make_unique to create unique_ptr
To avoid direct use new
, C++14 introduced std::make_unique
function templates for creation unique_ptr
. Using make_unique
not only makes the code cleaner, but also better avoids memory leaks and provides exception safety.
auto ptr = std::make_unique<int>(10); // 自动推导出类型为 std::unique_ptr<int>
1.7 Avoid raw pointer operations
Use unique_ptr
to avoid direct manipulation of raw pointers. Raw pointers may cause problems such as memory leaks and wild pointers. After use unique_ptr
, you can return the function with confidence knowing that no resources will be leaked.
std::unique_ptr<int> safe_func() {
return std::make_unique<int>(10);
}
2. shared_ptr shared smart pointer
std::shared_ptr
Is a smart pointer used to implement the concept of shared ownership. Multiple shared_ptr
can point to the same object. This object and its related resources will be released when "the last reference is destroyed". To perform this function, shared_ptr
a counting mechanism is used to ensure that the number of pointers to an object is kept track of.
2.1 Create shared_ptr
shared_ptr
The most straightforward way to create is to use std::make_shared
:
auto ptr = std::make_shared<int>(10);
In the above code, we create a shared_ptr
, which points to a dynamically allocated integer.
2.2 Shared Ownership
shared_ptr
The most critical feature is that multiple shared_ptr
can point to the same object, but no one is shared_ptr
more dominant than the others. Each shared_ptr
has equal ownership, and when the last one shared_ptr
stops pointing to an object, that object is deleted.
std::shared_ptr<int> ptr1 = std::make_shared<int>(10);
std::shared_ptr<int> ptr2 = ptr1; // 复制 ptr1;引用计数增加
// ptr1 和 ptr2 都销毁时,内存被释放
2.3 Reference counting
As mentioned earlier, shared_ptr
shared ownership is achieved through reference counting. Whenever a new shared_ptr
object is pointed to, the reference count is incremented; when shared_ptr
it is destroyed (or repointed to another object), the reference count is decremented. When the reference count reaches 0, the object is deleted.
We can use_count
view the current reference count through the method:
auto ptr1 = std::make_shared<int>(10);
std::cout << ptr1.use_count() << '\n'; // 输出:1
auto ptr2 = ptr1;
std::cout << ptr1.use_count() << '\n'; // 输出:2
Note, use_count
primarily for debugging purposes, it is not a constant time operation. And, even though we can see use_count
the value of , in a multi-threaded environment we can't use it to reliably tell whether there is other shared_ptr
, because after you check use_count
, there may be other shared_ptr
created or destroyed.
2.4 Custom deleter
Like unique_ptr
, shared_ptr
custom deleters are also supported:
auto deleter = [](int* ptr) {
std::cout << "Custom deleting. \n";
delete ptr;
};
std::shared_ptr<int> ptr(new int, deleter);
In the above code, we have provided a custom deleter function. When shared_ptr
it needs to delete the object it manages, it will call this deleter.
2.5 Direct use of make_shared vs shared_ptr
When creating shared_ptr
, it is recommended for std::make_shared
two reasons: performance and security. std::make_shared
Memory is allocated once to store objects and control blocks (containing information such as reference counts), which helps improve memory usage efficiency. shared_ptr
Using the constructor directly requires allocating memory twice, once for the object and once for the control block. In addition, std::make_shared
memory leaks due to exceptions can be better prevented.
2.6 Purpose of weak_ptr
std::shared_ptr
There is a related smart pointer type: std::weak_ptr
. weak_ptr
It exists to solve shared_ptr
the circular reference problem that may occur in . We discuss this in detail below weak_ptr
.
3. Shared_ptr circular reference problem
Although shared_ptr
provides the facility to automatically manage and delete dynamically allocated objects, it can also cause problems if used inappropriately. A typical problem is circular references.
Circular references occur when two or more objects hold each other's shared_ptr
. In this case, every object has a reference pointing to it shared_ptr
, so their reference count never reaches zero, even if they are actually no longer accessible.
The following code shows an example of this:
class B; // 预声明 B 类
class A {
public:
std::shared_ptr<B> ptr;
~A() {
std::cout << "A deleted\n"; }
};
class B {
public:
std::shared_ptr<A> ptr;
~B() {
std::cout << "B deleted\n"; }
};
int main() {
{
std::shared_ptr<A> a(new A());
std::shared_ptr<B> b(new B());
a->ptr = b;
b->ptr = a;
} // 当离开这个块时,a 和 b 的 shared_ptr 将被销毁,但是由于循环引用,A 和 B 对象并未被删除
return 0;
}
In the above code, we create two objects A and B, and let A hold B's shared_ptr
and B hold A's shared_ptr
. This creates a circular reference, so when we try to delete a and b, the A and B objects are not deleted because their reference count has not reached zero.
This is the circular reference problem. The result of this problem is a memory leak: although our intention is shared_ptr
to delete the object when it is destroyed, the object's memory is not released due to circular references.
4. weak_ptr solves circular references
std::weak_ptr
is a special smart pointer that points to an std::shared_ptr
object managed by , but it does not increment the object's reference count. Since std::weak_ptr
does not increment the reference count, it does not prevent the pointed-to object from being deleted. This is weak_ptr
how to solve the circular reference problem: shared_ptr
replace the one that caused the cycle with weak_ptr
.
The following code example shows how to use weak_ptr
to solve the circular reference problem mentioned earlier:
class B; // 预声明 B 类
class A {
public:
std::shared_ptr<B> ptr;
~A() {
std::cout << "A deleted\n"; }
};
class B {
public:
std::weak_ptr<A> ptr; // 注意这里我们使用了 weak_ptr 而不是 shared_ptr
~B() {
std::cout << "B deleted\n"; }
};
int main() {
{
std::shared_ptr<A> a(new A());
std::shared_ptr<B> b(new B());
a->ptr = b;
b->ptr = a; // B 现在持有 A 的 weak_ptr
} // 当离开这个块时,A 和 B 对象被成功删除
return 0;
}
In the above code, we create two objects A and B, but this time B holds A weak_ptr
's shared_ptr
. Therefore, even though A holds B's shared_ptr
, B does not hold A's shared_ptr
, so there is no circular reference, and when we try to delete a and b, both A and B objects will be deleted successfully.
Note: When you need to weak_ptr
get from shared_ptr
, you can use the function weak_ptr
of lock
. But this doesn't always succeed because the pointed object may have been deleted. Therefore, the returned shared_ptr
may be empty.
std::weak_ptr<int> weak;
{
auto shared = std::make_shared<int>(42);
weak = shared;
}
// 此时,shared 已经销毁,所以从 weak 中获取 shared_ptr 会得到一个空的 shared_ptr。
auto sharedFromWeak = weak.lock(); // 返回一个空的 shared_ptr
if (sharedFromWeak) {
// 这里的代码不会被执行,因为 sharedFromWeak 是空的。
}