创建持有已分配数组的unique_ptr的正确方法


74

创建一个unique_ptr来保存在免费存储中分配的数组的正确方法是什么?Visual Studio 2013默认情况下支持此功能,但是当我在Ubuntu上使用gcc版本4.8.1时,会出现内存泄漏和未定义的行为。

可以使用以下代码重现该问题:

#include <memory>
#include <string.h>

using namespace std;

int main()
{
    unique_ptr<unsigned char> testData(new unsigned char[16000]());

    memset(testData.get(),0x12,0);

    return 0;
}

Valgrind将给出以下输出:

==3894== 1 errors in context 1 of 1:
==3894== Mismatched free() / delete / delete []
==3894==    at 0x4C2BADC: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3894==    by 0x400AEF: std::default_delete<unsigned char>::operator()(unsigned char*) const (unique_ptr.h:67)
==3894==    by 0x4009D0: std::unique_ptr<unsigned char, std::default_delete<unsigned char> >::~unique_ptr() (unique_ptr.h:184)
==3894==    by 0x4007A9: main (test.cpp:19)
==3894==  Address 0x5a1a040 is 0 bytes inside a block of size 16,000 alloc'd
==3894==    at 0x4C2AFE7: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3894==    by 0x40075F: main (test.cpp:15)

12
不要在代码中放入疾病。使用std::fill
R. Martinho Fernandes 2014年

Answers:


124

使用T[]专业化:

std::unique_ptr<unsigned char[]> testData(new unsigned char[16000]());

请注意,在理想情况下,不必显式使用new实例化unique_ptr,从而避免潜在的异常安全隐患。为此,C ++ 14为您提供了std::make_unique功能模板。有关更多详细信息,请参见此出色的GOTW。语法为:

auto testData = std::make_unique<unsigned char[]>(16000);

1
只是要确保:如上所示创建的unique_ptrof T[],是否delete []对数据调用了正确的删除器(即)?我在他们指定了自定义删除器的地方看到了一个教程,所以我想知道这是否仍然是必需的,或者是否可以正确删除所有内容。
j00hi

2
@ j00hi是的。这就是拥有专业化的原因。
juanchopanza

1
@juanchopanza std :: make_unique <T []>调用T的默认构造函数。像上面的无符号字符这样的基本数据类型会发生什么?make_unique零是否初始化基本数据类型?当我打印“ testData”(上方)时,它什么也不打印。
卡蒂克·特里维克拉姆

2
@kartiktrivikram它的值初始化元素,对于具有默认构造函数的类型而言,这意味着它们是默认构造的;对于内置类型而言,它意味着零初始化。
juanchopanza

38

使用数组版本:

auto testData = std::unique_ptr<unsigned char[]>{ new unsigned char[16000] };

或者使用c ++ 14,一种更好的形式(VS2013已经拥有它):

auto testData = std::make_unique<unsigned char[]>( 16000 );

12
2条语句不相等。第二个值初始化数组,而第一个值创建未初始化的数组。从这个意义上讲,make_unique它并不总是比构造函数plus好new。这意味着在不需要用值初始化的用例中,因为对性能至关重要,因此构造函数版本更好。请注意,auto testData = unique_ptr<unsigned char[]> { new unsigned char[16000]() ];它等同于make_unique版本。
maxschlepzig

14

最有可能的更好的办法是使用std::vector<unsigned char>替代

#include <vector>
#include <string>

using namespace std;

int main()
{
    vector<unsigned char> testData(0x12, 0); // replaces your memset
    // bla    
}

优点是,这种方法不易出错,可以访问各种功能,例如轻松迭代,插入,达到容量后自动重新分配。

有一个警告:如果要大量移动数据,则std::vector成本会更高一些,因为它不仅会跟踪数据的大小,而且还会跟踪数据的大小和容量。

注意:您memset不执行任何操作,因为您使用零计数参数来调用它。


1
std::vector与手动内存管理相比,手动操作更容易出错。但是,如果OP知道足够使用a std::unique_ptr,我会说vector不再是明显的赢家。至少其内存占用量可能是其三倍多。例如,如果您在性能关键的代码中按值(移动)传递它,则可以计数。
Angew不再为2014年

2
@Angew一种更可能的情况是,OP被用来做原始指针分配,已经听说过unique_ptr,现在想“升级”到现代C ++。您的性能参数已得到很好的说明,但除了最关键的代码外,对所有参数均无关紧要。unique_ptr加号memset容易出错,尤其是如果将其推广到非POD类型时尤其如此。
TemplateRex

如果您愿意削弱主张(例如“最可能的更好方法...”)或提及大小/性能差异,您将获得投票;-)
Angew不再为

我看不到移动数据会带来多少成本,因为您只是通过引用传递了数据,这等同于传递一个std::unique_ptr。如果要复制数据,则可能会有一点(很小的)开销,但是您还可以选择数据移动到适当的位置。
加里克

@Galik我认为我所收录的Angew的评论很简单:astd::vector的大小是a的3倍std::unique_ptr,但它在便利性和数据封装方面有回报。
TemplateRex

1

好像是傻瓜,我会解释我的意思

class Object {
private :
    static int count;
public :
    Object() {
        cout << "Object Initialized " << endl;
        count++;
    }
    ~Object() {
        cout << "Object destroyed " << endl;
    }
    int print()
    {
        cout << "Printing" << endl;
        return count;
    }
};

int Object::count = 0;

int main(int argc,char** argv)
{
    // This will create a pointer of Object
    unique_ptr<Object> up2 = make_unique<Object>();  
    up2->print();
    // This will create a pointer to array of Objects, The below two are same. 
    unique_ptr<Object[]> up1 = std::make_unique<Object[]>(30);
    Object obj[30];
    cout << up1.get()[8].print();
    cout << obj[8].print();

    // this will create a array of pointers to obj. 
        unique_ptr<Object*[]> up= std::make_unique<Object*[]>(30);
        up.get()[5] = new Object();
        unique_ptr<Object> mk = make_unique<Object>(*up.get()[5]);
        cout << up.get()[5]->print();

        unique_ptr<unique_ptr<Object>[]> up3 =  std::make_unique<unique_ptr<Object>[]>(20);
        up3.get()[5] = make_unique<Object>();

    return 0;
}

这篇文章的目的是,您需要了解一些隐藏的细微的东西。创建对象数组与unique_ptr的对象数组相同。仅当您在参数中传递它时,它才会起作用。创建unique_ptr的对象指针数组也不是很有用。因此,在大多数情况下只需要使用两个以下的值即可。

unique_ptr<Object> obj;
//and 
unique_ptr<unique_ptr<Object>[]>= make_unique<unique_ptr<Object>[]>(20);

1
unsigned int size=16000;
std::unique_ptr<unsigned char[], std::default_delete<unsigned char[]>> pData(new unsigned char[size]);

4
尽管此代码可以回答问题,但提供有关此代码为何和/或如何回答问题的其他上下文,可以提高其长期价值。
adiga

1

大概像下面这样吗?

using namespace std;

int size = get_size();
int const init_value = 123;

unique_ptr<int[]> p = make_unique<int[]>(size)
fill(p.get(), p.get() + size, init_value);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.