286

std::unique_ptr支持数组,例如:

std::unique_ptr<int[]> p(new int[10]);

但需要吗?std::vector可能使用or更方便std::array

你觉得那个结构有什么用吗?

4

18 回答 18

304

有些人甚至没有使用std::vector分配器的奢侈。有些人需要一个动态大小的数组,所以std::array就不行了。有些人从已知返回数组的其他代码中获取数组;并且该代码不会被重写以返回 avector或其他东西。

通过允许unique_ptr<T[]>,您可以满足这些需求。

简而言之,您unique_ptr<T[]>需要时使用。当替代方案根本不适合您时。这是不得已而为之的工具。

于 2013-05-23T10:42:52.997 回答
160

需要权衡取舍,您可以选择与您想要的解决方案相匹配的解决方案。在我的头顶上:

初始大小

  • vectorunique_ptr<T[]>允许在运行时指定大小
  • array只允许在编译时指定大小

调整大小

  • array并且unique_ptr<T[]>不允许调整大小
  • vector

贮存

  • vector并将unique_ptr<T[]>数据存储在对象之外(通常在堆上)
  • array将数据直接存储在对象中

复印

  • arrayvector允许复制
  • unique_ptr<T[]>不允许复制

交换/移动

  • vector并且unique_ptr<T[]>有 O(1) 时间swap和移动操作
  • array有 O(n) 时间swap和移动操作,其中 n 是数组中的元素数

指针/引用/迭代器失效

  • array确保指针、引用和迭代器在对象处于活动状态时永远不会失效,即使在swap()
  • unique_ptr<T[]>没有迭代器;指针和引用仅swap()在对象处于活动状态时才会失效。(交换后,指针指向你交换的数组,所以在这个意义上它们仍然是“有效的”。)
  • vector可能使任何重新分配时的指针、引用和迭代器无效(并提供一些保证,重新分配只能发生在某些操作上)。

与概念和算法的兼容性

  • array并且vector都是容器
  • unique_ptr<T[]>不是容器

我不得不承认,这看起来像是使用基于策略的设计进行一些重构的机会。

于 2013-05-29T02:40:58.230 回答
88

您可能使用 a 的一个原因unique_ptr是,如果您不想支付对数组进行值初始化的运行时成本。

std::vector<char> vec(1000000); // allocates AND value-initializes 1000000 chars

std::unique_ptr<char[]> p(new char[1000000]); // allocates storage for 1000000 chars

构造std::vector函数std::vector::resize()并将值初始化T- 但如果是 POD new,则不会这样做。T

请参阅C++11 中的值初始化对象和 std::vector 构造函数

请注意,这vector::reserve不是替代方法:在 std::vector::reserve 之后访问原始指针是否安全?

这与 C 程序员可能会选择malloc.calloc

于 2014-07-20T17:19:30.190 回答
34

Anstd::vector可以被复制,同时unique_ptr<int[]>允许表达数组的唯一所有权。std::array另一方面,需要在编译时确定大小,这在某些情况下可能是不可能的。

于 2013-05-23T10:40:38.780 回答
28

Scott Meyers 在《有效的现代 C++》中这样说

for 数组的存在std::unique_ptr应该只对您感兴趣,因为std::array, std::vector,std::string几乎总是比原始数组更好的数据结构选择。我能想到的唯一一种情况std::unique_ptr<T[]>是,当您使用类似 C 的 API 时,该 API 返回指向您拥有所有权的堆数组的原始指针。

我认为查尔斯萨尔维亚的回答是相关的:这std::unique_ptr<T[]>是初始化一个在编译时大小未知的空数组的唯一方法。对于这种使用std::unique_ptr<T[]>.

于 2015-06-08T07:37:53.003 回答
19

std::vectorand相反std::arraystd::unique_ptr可以拥有一个 NULL 指针。
这在使用需要数组或 NULL 的 C API 时会派上用场:

void legacy_func(const int *array_or_null);

void some_func() {    
    std::unique_ptr<int[]> ptr;
    if (some_condition) {
        ptr.reset(new int[10]);
    }

    legacy_func(ptr.get());
}
于 2015-08-26T18:14:42.030 回答
12

我不能强烈反对已接受答案的精神。“不得已的工具”?离得很远!

在我看来,与 C 和其他一些类似语言相比,C++ 最强大的特性之一是能够表达约束,以便可以在编译时检查它们并防止意外误用。因此,在设计结构时,问问自己它应该允许哪些操作。应该禁止所有其他用途,最好是可以静态实现这些限制(在编译时),以免误用导致编译失败。

因此,当需要一个数组时,以下问题的答案会指定它的行为: 1. 它的大小是 a) 在运行时是动态的,还是 b) 是静态的,但只在运行时知道,或者 c) 是静态的,在编译时是已知的?2. 数组能不能入栈?

根据答案,这是我认为此类数组的最佳数据结构:

       Dynamic     |   Runtime static   |         Static
Stack std::vector      unique_ptr<T[]>          std::array
Heap  std::vector      unique_ptr<T[]>     unique_ptr<std::array>

是的,我认为unique_ptr<std::array>也应该考虑,也不是万不得已的工具。想想什么最适合你的算法。

vector.data()所有这些都通过指向数据数组 ( / array.data()/ uniquePtr.get())的原始指针与普通 C API 兼容。

PS 除了上述考虑之外,还有一个所有权:std::arraystd::vector具有值语义(对复制和按值传递具有本机支持),而unique_ptr<T[]>只能移动(强制单一所有权)。两者都可以在不同的情况下有用。相反,纯静态数组int[N]new int[10]如果这还不够,普通的动态数组也无法查询它们的大小——内存损坏和安全漏洞的额外机会。

于 2019-12-20T14:20:21.103 回答
11

简而言之:它是迄今为止内存效率最高的。

Astd::string带有一个指针、一个长度和一个“短字符串优化”缓冲区。但是我的情况是我需要在一个我有数十万个的结构中存储一个几乎总是空的字符串。在 C 语言中,我只会使用char *,并且大多数情况下它都是 null 。这也适用于 C++,除了 achar *没有析构函数,也不知道删除自己。相比之下,std::unique_ptr<char[]>当超出范围时,a 将自行删除。emptystd::string占用 32 个字节,而 emptystd::unique_ptr<char[]>占用 8 个字节,也就是其指针的大小。

最大的缺点是,每次我想知道字符串的长度时,我都要调用strlen它。

于 2015-12-18T08:38:42.917 回答
10

我曾经unique_ptr<char[]>实现在游戏引擎中使用的预分配内存池。这个想法是提供预先分配的内存池,而不是动态分配来返回碰撞请求结果和粒子物理等其他东西,而不必在每一帧分配/释放内存。对于需要内存池来分配生命周期有限(通常为 1、2 或 3 帧)且不需要销毁逻辑(仅内存释放)的对象的场景,这非常方便。

于 2013-05-28T23:04:35.470 回答
10

在一些Windows Win32 API调用中可以找到一个常见模式,其中使用std::unique_ptr<T[]>可以派上用场,例如,当您不确切知道调用某些 Win32 API 时输出缓冲区应该有多大时(这将在其中写入一些数据该缓冲区):

// Buffer dynamically allocated by the caller, and filled by some Win32 API function.
// (Allocation will be made inside the 'while' loop below.)
std::unique_ptr<BYTE[]> buffer;

// Buffer length, in bytes.
// Initialize with some initial length that you expect to succeed at the first API call.
UINT32 bufferLength = /* ... */;

LONG returnCode = ERROR_INSUFFICIENT_BUFFER;
while (returnCode == ERROR_INSUFFICIENT_BUFFER)
{
    // Allocate buffer of specified length
    buffer.reset( BYTE[bufferLength] );
    //        
    // Or, in C++14, could use make_unique() instead, e.g.
    //
    // buffer = std::make_unique<BYTE[]>(bufferLength);
    //

    //
    // Call some Win32 API.
    //
    // If the size of the buffer (stored in 'bufferLength') is not big enough,
    // the API will return ERROR_INSUFFICIENT_BUFFER, and the required size
    // in the [in, out] parameter 'bufferLength'.
    // In that case, there will be another try in the next loop iteration
    // (with the allocation of a bigger buffer).
    //
    // Else, we'll exit the while loop body, and there will be either a failure
    // different from ERROR_INSUFFICIENT_BUFFER, or the call will be successful
    // and the required information will be available in the buffer.
    //
    returnCode = ::SomeApiCall(inParam1, inParam2, inParam3, 
                               &bufferLength, // size of output buffer
                               buffer.get(),  // output buffer pointer
                               &outParam1, &outParam2);
}

if (Failed(returnCode))
{
    // Handle failure, or throw exception, etc.
    ...
}

// All right!
// Do some processing with the returned information...
...
于 2014-08-25T19:52:54.140 回答
10

我遇到了一个我必须使用的案例std::unique_ptr<bool[]>,它在 HDF5 库中(一个用于高效二进制数据存储的库,在科学中使用了很多)。一些编译器(在我的例子中是 Visual Studio 2015)提供压缩std::vector<bool>(通过在每个字节中使用 8 个布尔值),这对于 HDF5 之类的东西来说是一场灾难,它不关心这种压缩。使用std::vector<bool>时,HDF5 最终会因为压缩而读取垃圾。

猜猜谁在那里救援,在std::vector没有工作的情况下,我需要干净地分配一个动态数组?:-)

于 2017-08-16T13:05:44.320 回答
6

std::unique_ptr<T[]>到目前为止,还没有在回复中提到允许和使用的另一个原因:它允许您前向声明数组元素类型。

当您想要最小化#include标题中的链接语句(以优化构建性能)时,这很有用。

例如 -

myclass.h:

class ALargeAndComplicatedClassWithLotsOfDependencies;

class MyClass {
   ...
private:
   std::unique_ptr<ALargeAndComplicatedClassWithLotsOfDependencies[]> m_InternalArray;
};

我的类.cpp:

#include "myclass.h"
#include "ALargeAndComplicatedClassWithLotsOfDependencies.h"

// MyClass implementation goes here

通过上述代码结构,任何人都可以#include "myclass.h"使用.MyClassMyClass::m_InternalArray

如果m_InternalArray被分别声明为 astd::array<ALargeAndComplicatedClassWithLotsOfDependencies>或 a std::vector<...>,则结果将尝试使用不完整的类型,这是编译时错误。

于 2018-02-16T01:31:06.233 回答
3
  • 出于二进制兼容性的原因,您需要您的结构仅包含一个指针。
  • 您需要与返回分配的内存的 API 交互new[]
  • 您的公司或项目有一条禁止使用的一般规则std::vector,例如,防止粗心的程序员意外引入副本
  • 您想防止粗心的程序员在这种情况下意外引入副本。

有一个一般规则,即 C++ 容器比使用指针滚动你自己的容器更受欢迎。这是一般规则;它有例外。还有更多; 这些只是例子。

于 2014-08-28T16:30:05.733 回答
3

当您在设备中分配内存时,要回答那些认为您“必须”使用vector而不是unique_ptr我在 GPU 上进行 CUDA 编程的案例,您必须使用指针数组(使用cudaMalloc)。然后,在 Host 中检索此数据时,您必须再次获取指针,并且unique_ptr可以轻松处理指针。double*转换为的额外成本vector<double>是不必要的,并且会导致性能损失。

于 2018-01-30T16:08:18.690 回答
2

当您只能通过现有 API(想想窗口消息或与线程相关的回调参数)戳单个指针时,它们可能是最正确的答案,这些 API 在被“捕获”到舱口的另一侧后具有一定的生命周期,但这与调用代码无关:

unique_ptr<byte[]> data = get_some_data();

threadpool->post_work([](void* param) { do_a_thing(unique_ptr<byte[]>((byte*)param)); },
                      data.release());

我们都希望事情对我们有益。C++ 用于其他时间。

于 2013-06-07T11:35:26.583 回答
2

unique_ptr<char[]>可以在您想要 C 的性能和 C++ 的便利性的地方使用。考虑您需要对数百万(好吧,如果您还不信任的话,数十亿)字符串进行操作。将它们中的每一个存储在单独的stringvector<char>对象中对于内存(堆)管理例程来说将是一场灾难。特别是如果您需要多次分配和删除不同的字符串。

但是,您可以分配一个缓冲区来存储那么多字符串。您不喜欢char* buffer = (char*)malloc(total_size);的原因很明显(如果不明显,请搜索“为什么使用智能 ptrs”)。你宁愿喜欢unique_ptr<char[]> buffer(new char[total_size]);

以此类推,相同的性能和便利性考虑适用于非char数据(考虑数百万个向量/矩阵/对象)。

于 2016-08-18T10:52:19.317 回答
0

如果您需要一个不可复制构造的动态对象数组,那么指向数组的智能指针是可行的方法。例如,如果您需要一个原子数组怎么办。

于 2017-11-09T17:19:54.500 回答
0

tl;博士:这是一个穷人的std::dynarray

让我们将std::unique_ptr<T[]>a 视为一个容器。虽然它确实由于缺少大小字段而被削弱,并且不能直接用作容器,但它占据了标准库可用容器的“参数空间”中的一个点,该点不被其他任何人共享,适当的,容器 - 即使您将 Boost 添加到组合中。

如果您查看我对广泛可用的类似矢量/连续容器的比较,并寻找与以下相同的功能std::unique_ptr

  • 堆上分配
  • 容量在编译时不固定
  • 建造后无法更改容量(无需完全清除容器)

您会看到没有其他容器提供所有这些,除了std::dynarray; 但这实际上不在标准库中——它应该进入 C++14,但最终被拒绝了。

我不仅仅是在猜测。即使在 SO 上,这也是偶尔描述事物的方式;请参阅@KerrekSB 2013 年对此问题的回答。

于 2021-12-17T16:19:56.323 回答