对于带有数组的unique_ptr有什么用处吗?

时间:2013-05-23 10:35:58

标签: c++ c++11 smart-pointers

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

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

但它需要吗?可能使用std::vectorstd::array更方便。

你觉得这个结构有用吗?

17 个答案:

答案 0 :(得分:219)

有些人没有使用std::vector的奢侈,即使使用分配器也是如此。有些人需要一个动态大小的数组,所以std::array已经出局了。有些人从其他已知返回数组的代码中获取数组;并且不会重写该代码以返回vector或其他内容。

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

简而言之,当您需要时,请使用unique_ptr<T[]>。当替代品根本不适合你。这是最后的工具。

答案 1 :(得分:106)

有权衡,你选择符合你想要的解决方案。在我的头顶:

初始尺寸

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

调整

  • arrayunique_ptr<T[]>不允许调整大小
  • vector

存储

  • vectorunique_ptr<T[]>将数据存储在对象外部(通常在堆上)
  • array将数据直接存储在对象

复制

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

切换/移动

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

指针/引用/迭代器失效

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

与概念和算法的兼容性

  • arrayvector都是容器
  • unique_ptr<T[]>不是容器

我必须承认,这看起来像是通过基于策略的设计进行重构的机会。

答案 2 :(得分:60)

您可能使用unique_ptr的一个原因是,如果您不想支付value-initializing数组的运行时费用。

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 - 但如果new是POD,T将不会这样做。

请参阅Value-Initialized Objects in C++11 and std::vector constructor

请注意vector::reserve不是替代方案:Is accessing the raw pointer after std::vector::reserve safe?

这与C程序员选择malloc而不是calloc的原因相同。

答案 3 :(得分:28)

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

答案 4 :(得分:20)

Scott Meyers在Effective Modern C ++中有这个说法

  

数组的std::unique_ptr的存在应该只是你的智力兴趣,因为std::array,   std::vectorstd::string几乎总是比原始数组更好的数据结构选择。关于std::unique_ptr<T[]>有意义的唯一情况是,当您使用类似C的API返回原始指针到您认为拥有所有权的堆数组时。

我认为Charles Salvia的答案是相关的:std::unique_ptr<T[]>是初始化一个空数组的唯一方法,该数组的大小在编译时是未知的。 Scott Meyers对使用std::unique_ptr<T[]>的动机有什么看法?

答案 5 :(得分:12)

std::vectorstd::array相反,std::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());
}

答案 6 :(得分:9)

可以在some 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...
...

答案 7 :(得分:9)

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

答案 8 :(得分:8)

简而言之:到目前为止,它的内存效率最高。

std::string附带一个指针,一个长度和一个&#34;短字符串优化&#34;缓冲。但我的情况是我需要存储一个几乎总是空的字符串,在一个我有成千上万的结构中。在C中,我只使用char *,并且在大多数情况下它将为空。这也适用于C ++,除了char *没有析构函数,并且不知道删除自己。相比之下,当std::unique_ptr<char[]>超出范围时,std::string将自行删除。空std::unique_ptr<char[]>占用32个字节,但空strlen占用8个字节,即与其指针的大小完全相同。

最大的缺点是,每当我想知道字符串的长度时,我就必须在其上调用 var data = [[ {"claimNumber":"6396","itemNumber":"1","dateAssessed":"20/10/2015"}, {"claimNumber":"2208","itemNumber":"1","dateAssessed":"20/09/2015"}, {"claimNumber":"918","itemNumber":"1","dateAssessed":"12/09/2015"}, {"claimNumber":"2208","itemNumber":"1","dateAssessed":"20/09/2015"}, {"claimNumber":"2206","itemNumber":"1","dateAssessed":"20/09/2015"}, {"claimNumber":"2205","itemNumber":"1","dateAssessed":"20/09/2015"}, {"claimNumber":"2208","itemNumber":"1","dateAssessed":"20/09/2015"}, ]]; function arrange(data) { var res = {}; for(var i = 0, obj; obj = data[i]; i++) { if(!res[obj.dateAssessed]) { res[obj.dateAssessed] = []; } res[obj.dateAssessed].push(obj); } return res; } /* it returns like: { 12/09/2015: [{ ... }], 20/09/2015: [{ ... }], 20/10/2015: [{ ... }] } */ arrange(data[0]);

答案 9 :(得分:5)

我遇到了一个案例,我不得不使用std::unique_ptr<bool[]>,它位于HDF5库中(一个用于高效二进制数据存储的库,在科学中经常使用)。一些编译器(在我的情况下是Visual Studio 2015)provide compression of std::vector<bool>(在每个字节中使用8个bool),这对HDF5这样的事情来说是一个灾难,它不关心压缩。对于std::vector<bool>,由于压缩,HDF5最终读取了垃圾。

std::vector无法正常工作的情况下猜猜是谁在那里进行救援,我需要干净地分配一个动态阵列? : - )

答案 10 :(得分:3)

回答那些认为你的人必须&#34;使用vector而不是unique_ptr我在GPU上进行CUDA编程时有一个案例,当您在Device中分配内存时,必须使用指针数组(cudaMalloc)。 然后,当在Host中检索此数据时,您必须再次寻找指针并且unique_ptr可以轻松处理指针。 将double*转换为vector<double>的额外费用是不必要的,并且会导致性能损失。

答案 11 :(得分:3)

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

当您希望最小化标头中的链式#include语句(以优化构建性能)时,这非常有用。

例如 -

  

myclass.h:

class ALargeAndComplicatedClassWithLotsOfDependencies;

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

myclass.cpp:

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

// MyClass implementation goes here

使用上述代码结构,任何人都可以#include "myclass.h"并使用MyClass,而无需包含MyClass::m_InternalArray所需的内部实现依赖项。

如果m_InternalArray分别声明为std::array<ALargeAndComplicatedClassWithLotsOfDependencies>std::vector<...>,则会尝试使用不完整类型,这是编译时错误。< / p>

答案 12 :(得分: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 ++是其他时间。

答案 13 :(得分:2)

  • 由于二进制兼容性的原因,您需要只包含指针的结构。
  • 您需要与返回由new[]
  • 分配的内存的API进行交互
  • 您的公司或项目有一般规则禁止使用std::vector,以防止粗心的程序员意外地引入副本
  • 您希望防止粗心的程序员在此实例中意外地引入副本。

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

答案 14 :(得分:1)

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

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

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

答案 15 :(得分:0)

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

答案 16 :(得分:0)

我不能完全不同意接受的答案的精神。 “最后的手段”?距离它远!

我认为,与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兼容。

P。 S.除了上述考虑之外,还有一个所有权:std::arraystd::vector具有值语义(对值的复制和传递具有本机支持),而unique_ptr<T[]>仅可移动(强制实行单一所有权)。两者在不同的情况下都可能有用。相反,普通静态数组(int[N])和普通动态数组(new int[10])两者都不提供,因此应尽可能避免-在绝大多数情况下应该这样做。如果这还不够的话,普通动态数组也无法查询其大小-内存损坏和安全漏洞的额外机会。