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

fen*_*fen 219 c++ smart-pointers c++11

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

std::unique_ptr<int[]> p(new int[10]);
Run Code Online (Sandbox Code Playgroud)

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

你觉得这个结构有用吗?

Nic*_*las 237

std::vector即使有分配器,有些人也没有使用的奢侈品.有些人需要一个动态大小的数组,所以std::array出来了.有些人从其他已知返回数组的代码中获取数组; 并且该代码不会被重写以返回a vector或者某个东西.

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

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

  • 这是不使用vector的原因:sizeof(std :: vector <char>)== 24; sizeof(std :: unique_ptr <char []>)== 8 (58认同)
  • @NoSenseEtAl:我不确定"有些人不被允许这样做"的哪一部分会让你失望.有些项目有非常具体的要求,其中可能是"你不能使用`vector`".你可以争辩这些是否是合理的要求,但你不能否认它们*存在*. (25认同)
  • 世界上没有理由为什么有人不能使用`std :: vector`如果他们可以使用`std :: unique_ptr`. (20认同)
  • @DanNissenbaum另外一些硬实时系统根本不允许使用动态内存分配,因为系统调用导致的延迟可能在理论上没有限制,你无法证明程序的实时行为.或者界限可能太大会破坏您的WCET限制.虽然这里不适用,因为它们不会使用`unique_ptr`,但这些项目确实存在. (15认同)
  • @DanNissenbaum这些项目存在.一些受到严格审查的行业,例如航空或国防,标准库是禁止的,因为很难核实并证明任何管理机构设定规则是正确的.您可能会争辩说标准库已经过充分测试,我同意您的意见,但您和我都没有制定规则. (10认同)
  • 好吧,我明白了,我认为奢侈品你的意思是考虑因素,而不是无知:) (6认同)
  • @Engineero:"*添加/删除std :: vector中的元素不会.*"实际上,确实如此.只要为要添加的所有元素保留足够的空间,`vector :: push_back`将花费一致的时间. (5认同)
  • 我很好奇,如果评论*某些项目有非常具体的要求,其中可能是"你没有使用'vector`"*纯粹是理论上的,或者它是基于已知的情况,如果这样的限制可能是合理的. (4认同)
  • 不使用向量的唯一原因是,如果您不想支付初始化数组的价值. (4认同)
  • 许多预先存在的库和OS api只接受和输出数据指针.有时大小不是预先确定的,因此需要动态分配该阵列.如果是这样的话,std :: unique_ptr是一个让它异常安全的好方法. (4认同)
  • @MilesRout:如果T不是可复制构造的(例如,push_back和emplace_back不能编译),则不能使用vector <T>,但仍然可以使用unique_ptr <T []>. (3认同)
  • @MilesRout,@ DanNissenbaum - 您可能还有一个API约束,就像您无法更改签名一样.因此,您希望使用RIAA的自动指针和异常安全性,并使用`release()`完成该函数以返回指向调用者的指针.我正在寻找的代码也是如此,因为它不能破坏外部调用者的API. (3认同)
  • 或者,如果你想确保作为一个政策问题没有人忘记&或std :: move并且意外地*复制*整个事情.这不是什么类型检查? (2认同)
  • 我认为这根本不是有人可以使用 std::vector 而不是 std::unique_ptr 的问题。我对某些数组使用 std::unique_ptr&lt;T[]&gt; ,因为我可以确定如果抛出异常,它将被正确清理。 (2认同)
  • @Syncopated:FYI:即使`T`不是可复制构造的,你也可以*使用`vector <T>`.你不能用这样的`T`调用*`vector :: push_back`,但是你可以肯定地使用`vector :: emplace_back`. (2认同)

Pse*_*nym 115

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

初始大小

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

调整

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

存储

  • vector并将unique_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 可能会在任何重新分配时使指针,引用和迭代器无效(并提供一些保证重新分配只能在某些操作上发生).

与概念和算法的兼容性

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

我不得不承认,这似乎是对基于策略的设计进行重构的机会.

  • 假设您有一个迭代器,一个指针或对`vector`元素的引用.然后你增加那个`vector`的大小或容量,以便强制重新分配.然后迭代器,指针或引用不再指向`vector`的元素.这就是"无效"的含义.这个问题不会发生在`array`中,因为没有"重新分配".实际上,我刚刚注意到了一个细节,我编辑它以适应. (3认同)
  • @rubenvb 当然可以,但是您不能(例如)直接使用基于范围的 for 循环。顺便说一句,与普通的“T[]”不同,大小(或等效信息)必须在某处徘徊,以便“operator delete[]”正确销毁数组的元素。如果程序员可以访问它会很好。 (3认同)
  • 我不确定在_指针失效_的上下文中我是否理解您的意思。这是关于指向对象本身的指针,还是指向元素的指针?或者是其他东西?你从一个数组中得到了什么样的保证,而你从一个向量中得不到? (2认同)
  • 好的,不会因为数组中的重新分配或 `unique_ptr&lt;T[]&gt;` 而失效,因为没有重新分配。但是当然,当数组超出范围时,指向特定元素的指针仍然会失效。 (2认同)

Cha*_*via 65

您可能使用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
Run Code Online (Sandbox Code Playgroud)

std::vector构造函数和std::vector::resize()将值初始化T-但new不会做,如果T是一个POD.

请参阅C++ 11和std :: vector构造函数中的Value-Initialized Objects

注意,vector::reserve这里不是替代方法:在std :: vector :: reserve safe之后访问原始指针吗?

这是同样的原因,C程序员可能选择malloccalloc.

  • 另一种可能性是为“std::vector”提供一个[自定义分配器](/sf/answers/1117675681/),它可以避免构造“std::is_trivially_default_constructible”类型和销毁对象它们是 `std::is_trivially_destructible`,尽管严格来说这违反了 C++ 标准(因为此类类型不是默认初始化的)。 (2认同)

And*_*owl 30

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

  • `std :: vector`比`std :: unique_ptr`有更多的开销 - 它使用~3个指针代替~1.`std :: unique_ptr`会阻止复制构造,但会启用移动构造,如果在语义上只能移动但不复制的数据,则会感染包含数据的`class`.对*无效*的数据进行操作实际上会使您的容器类更糟,并且"只是不使用它"并不能消除所有的错误.必须将你的`std :: vector`的每个实例放入一个手动禁用`move`的类中,这很令人头疼.`std :: unique_ptr <std :: array>`有一个`size`. (8认同)
  • @NicolBolas:我不明白.人们可能想要防止出于同样的原因,为什么会使用`unique_ptr`而不是`shared_ptr`.我错过了什么吗? (4认同)
  • `unique_ptr`不仅可以防止意外误用.它比`shared_ptr`更小,开销更小.关键在于,虽然在类中防止"滥用"的语义很好,但这并不是使用特定类型的唯一原因.并且`vector`作为数组存储比`unique_ptr <T []>`更有用,如果除了它有*size*之外的其他原因. (4认同)
  • 我认为我明确指出:有*其他原因*使用特定类型而不是.就像有理由在可能的情况下更喜欢`vector`而不是'unique_ptr <T []>`,而不只是说,"你不能复制它",因此当你不要时,选择`unique_ptr <T []>`我想要副本.阻止某人做错事并不一定是选择课程的最重要原因. (3认同)
  • 仅仅因为某些东西*可以被复制并不意味着必须这样做. (2认同)

new*_*ing 22

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

的存在std::unique_ptr对数组应该只有智慧感兴趣的是你的,因为std::array, std::vector,std::string几乎总是更好的数据结构的选择,要比原阵列.关于唯一可以想到的情况std::unique_ptr<T[]>是,当你使用类似C的API时,它返回一个原始指针,指向你认为拥有的堆数组.

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

  • 听起来他只是根本没有设想一些用例,即大小固定但在编译时未知的缓冲区,和/或我们不允许复制的缓冲区。还有效率可能是它更喜欢`vector` /sf/answers/1739708911/的可能原因。 (3认同)

geo*_*rge 14

std::vector和相反std::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());
}
Run Code Online (Sandbox Code Playgroud)


小智 10

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


Vio*_*ffe 10

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

在我看来,与 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>
Run Code Online (Sandbox Code Playgroud)

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

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

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


Mr.*_*C64 9

一些 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...
...
Run Code Online (Sandbox Code Playgroud)


jor*_*own 9

简而言之:它是迄今为止最节省内存的.

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

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


The*_*ist 8

我遇到了不得不使用std::unique_ptr<bool[]>HDF5库(用于高效二进制数据存储的库,在科学上使用了很多)的情况。某些编译器(在我的情况下std::vector<bool> Visual Studio 2015)提供压缩(通过在每个字节中使用8个布尔值),这对于诸如HDF5之类的东西来说是灾难性的,它并不关心这种压缩。使用std::vector<bool>,HDF5最终由于该压缩而读取垃圾。

猜猜谁在那儿进行救援,以防万一,std::vector但是我没有必要分配一个动态数组?:-)


小智 6

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

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

例如 -

我的类.h:

class ALargeAndComplicatedClassWithLotsOfDependencies;

class MyClass {
   ...
private:
   std::unique_ptr<ALargeAndComplicatedClassWithLotsOfDependencies[]> m_InternalArray;
};
Run Code Online (Sandbox Code Playgroud)

我的类.cpp:

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

// MyClass implementation goes here
Run Code Online (Sandbox Code Playgroud)

有了上面的代码结构,任何人都可以#include "myclass.h"和使用MyClass,而不必包含MyClass::m_InternalArray.

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