std::unique_ptr
支持数组,例如:
std::unique_ptr<int[]> p(new int[10]);
但它需要吗?可能使用std::vector
或std::array
更方便。
你觉得这个结构有用吗?
答案 0 :(得分:219)
有些人没有使用std::vector
的奢侈,即使使用分配器也是如此。有些人需要一个动态大小的数组,所以std::array
已经出局了。有些人从其他已知返回数组的代码中获取数组;并且不会重写该代码以返回vector
或其他内容。
通过允许unique_ptr<T[]>
,您可以满足这些需求。
简而言之,当您需要时,请使用unique_ptr<T[]>
。当替代品根本不适合你。这是最后的工具。
答案 1 :(得分:106)
有权衡,你选择符合你想要的解决方案。在我的头顶:
初始尺寸
vector
和unique_ptr<T[]>
允许在运行时指定大小array
仅允许在编译时指定大小调整
array
和unique_ptr<T[]>
不允许调整大小vector
存储
vector
和unique_ptr<T[]>
将数据存储在对象外部(通常在堆上)array
将数据直接存储在对象复制
array
和vector
允许复制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[]>
不是容器我必须承认,这看起来像是通过基于策略的设计进行重构的机会。
答案 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::vector
,std::string
几乎总是比原始数组更好的数据结构选择。关于std::unique_ptr<T[]>
有意义的唯一情况是,当您使用类似C的API返回原始指针到您认为拥有所有权的堆数组时。
我认为Charles Salvia的答案是相关的:std::unique_ptr<T[]>
是初始化一个空数组的唯一方法,该数组的大小在编译时是未知的。 Scott Meyers对使用std::unique_ptr<T[]>
的动机有什么看法?
答案 5 :(得分:12)
与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());
}
答案 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[]
std::vector
,以防止粗心的程序员意外地引入副本一般来说,C ++容器比使用指针滚动自己更受欢迎。这是一般规则;它有例外。还有更多;这些只是一些例子。
答案 14 :(得分:1)
unique_ptr<char[]>
可用于您想要C的性能和C ++的便利性。考虑一下你需要对数百万的字符串进行操作(好吧,如果你还不信任,还需要数十亿)。将它们中的每一个存储在单独的string
或vector<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::array
和std::vector
具有值语义(对值的复制和传递具有本机支持),而unique_ptr<T[]>
仅可移动(强制实行单一所有权)。两者在不同的情况下都可能有用。相反,普通静态数组(int[N]
)和普通动态数组(new int[10]
)两者都不提供,因此应尽可能避免-在绝大多数情况下应该这样做。如果这还不够的话,普通动态数组也无法查询其大小-内存损坏和安全漏洞的额外机会。