智能指针#
C++ 中的智能指针是一种用于管理动态分配的对象的指针。它们提供了自动内存管理,可以帮助避免内存泄漏和悬空指针的问题。C++ 标准库提供了两种主要的智能指针:std::unique_ptr
和std::shared_ptr
。
std::unique_ptr
:
std::unique_ptr
是一种独占所有权的智能指针。它确保只有一个指针可以访问给定的资源。当std::unique_ptr
超出范围或被删除时,它会自动释放所管理的对象。它不能被复制,但可以通过移动语义传递所有权。使用std::make_unique
函数可以方便地创建std::unique_ptr
对象。
{
std::unique_ptr<int> ptr = std::make_uniqure<int>(42);
std::cout << *ptr << std::endl;
}
// ptr 超出范围后,其管理的对象会被自动释放
std::shared_ptr
:
std::shared_ptr
是一种共享所有权的智能指针。它可以被多个指针共享,并且会跟踪有多少个指针引用了给定的资源。只有当最后一个std::shared_ptr
超出范围或被删除时,才会释放所管理的对象。它可以通过复制来共享所有权,也可以通过移动语义传递所有权。使用std::make_shared
函数可以方便地创建std::shared_ptr
对象。
{
std::unique_ptr ptr1 = std::make_uniqure(42);
std::unique_ptr ptr2 = ptr1; // 共享所有权
}
//ptr1 和 ptr2 超出范围后,其管理的对象会被自动释放
智能指针的使用可以有效地管理动态分配的对象,避免内存泄漏和悬空指针的问题。但需要注意,智能指针并不能解决所有的内存管理问题,例如循环引用可能导致资源无法释放。因此,在使用智能指针时,仍然需要谨慎设计和管理对象的生命周期。
关于共享指针, 以 std::make_shared<Chunk>(position)
为例#
std::make_shared<Chunk>(position)
是一个使用 std::make_shared
函数创建 Chunk
对象的语法。
具体来说,这行代码使用了尖括号 <Chunk>
来指定模板参数,告诉 std::make_shared
函数我们要创建的对象的类型是 Chunk
。然后,括号中的 position
是传递给 Chunk
构造函数的参数。
std::make_shared
是一个模板函数,它的定义如下:
template<typenameT, typename... Args>
shared_ptr<T> make_shared(Args&&... args);
它接受一个可变数量的参数 Args&&... args
,这些参数将被传递给 T
类型的构造函数来创建对象。在这个例子中,T
是 Chunk
类型,Args
是 position
的类型。
std::make_shared
函数返回一个 shared_ptr<T>
类型的共享指针,它指向通过传递的参数构造的 T
类型对象。
因此,std::make_shared<Chunk>(position)
这行代码的作用是创建一个 Chunk
类型的对象,并使用共享指针来管理该对象的生命周期。同时,将 position
作为参数传递给 Chunk
的构造函数,以便在创建对象时进行初始化。
实际上,std::make_shared
主要用于构造新的对象。它会在堆上动态分配内存,并使用传递的参数来调用对象的构造函数。
如果有一个已经存在的实例,并且想要将其包装在一个共享指针中进行管理,可以使用 std::shared_ptr
的构造函数或 std::make_shared
的变体来实现。
使用 std::shared_ptr
的构造函数可以将一个已经存在的指针包装成共享指针。#
例如:
Chunk* existingChunk = newChunk(position);
std::shared_ptr<Chunk> sharedChunk(existingChunk);
在这个例子中,existingChunk
是一个已经存在的 Chunk
对象的指针,通过将它传递给 std::shared_ptr
构造函数,可以创建一个共享指针 sharedChunk
来管理该对象的生命周期。
在这种情况下,不应该继续使用
existingChunk
,因为一旦sharedChunk
被销毁(或者最后一个拥有它的共享指针被销毁),它指向的内存会被自动释放。如果在那之后尝试访问existingChunk
,将会导致未定义行为,比如访问已经释放的内存。
(不推荐的!)
另一种方法是使用 std::make_shared
的变体,即 std::allocate_shared
:
Chunk* existingChunk = newChunk(position);
std::shared_ptr<Chunk> sharedChunk = std::allocate_shared<Chunk>(std::allocator<Chunk>(), *existingChunk);
在这个例子中,使用 std::allocate_shared<Chunk>(std::allocator<Chunk>(), *existingChunk)
会创建一个新的 Chunk
实例,并且这个新实例是通过复制 existingChunk
指向的对象来初始化的。这里并没有发生将普通指针 existingChunk
转换为共享指针的行为,而是发生了对象的复制。std::allocate_shared
使用提供的对象(即 *existingChunk
)作为复制构造函数的参数来创建新对象,并由返回的共享指针管理。
通过 std::allocate_shared
创建一个对象的副本并同时保留原始指针指向的对象 —— 通常是不推荐的行为。这种做法会导致以下几个问题:
-
内存管理复杂化:这种方法创建了两个独立的对象实例,一个由原始指针管理,另一个由共享指针管理。这就需要你分别管理这两个对象的生命周期,增加了内存泄漏的风险。
-
设计意图不明确:智能指针(如 std::shared_ptr)的引入主要是为了简化内存管理,通过自动管理对象的生命周期来减少内存泄漏和悬垂指针的风险。通过复制对象而不是接管原始指针,你实际上没有利用智能指针的主要优势。
-
性能开销:复制对象可能涉及显著的性能开销,尤其是当对象较大或复制操作代价高昂时。如果没有必要创建对象的副本,这种开销是可以避免的。