在C++11中,我该如何调用new并为对象预留足够的内存?

8

我有一个被描述成这样的类:

class Foo {
    int size;
    int data[0];

public:
    Foo(int _size, int* _data) : size(_size) {
        for (int i = 0 ; i < size ; i++) {
            data[i] = adapt(_data[i]);
        }
    }

    // Other, uninteresting methods
}

我无法修改那个类的设计。

如何创建该类的实例?在调用构造函数之前,我必须使其预留足够的内存来存储其数据,因此它必须在堆上而不是栈上。我猜想我需要这样的东西:

Foo* place = static_cast<Foo*>(malloc(sizeof(int) + sizeof(int) * size));
*place = new Foo(size, data);  // I mean : "use the memory allocated in place to do your stuff !"

但是我找不到让它工作的方法。
编辑:正如评论者所指出的,这不是一个非常好的总体设计(使用了非标准技巧,如data[0]),不幸的是这是一个我被迫使用的库...

4
手动分配内存并使用“placement new”可能会有所帮助。 - DCoder
13
int data[0] 不是合法的 C++ 语法。 - Kerrek SB
3
放置 new 语法:new (address) Type(arguments...); 这里的 addressvoid* 类型。使用放置 new 创建的任何东西必须通过显式调用析构函数来销毁;我怀疑你不想手动执行这个过程,因此最好编写一个包装器来处理 Foo 的正确分配/释放。 - Matthieu M.
1
我手头的草案标准中有一个[dcl.array]章节,其中明确说明了数组:“如果存在常量表达式(5.19),它必须是整数常量表达式,并且其值必须大于零。” - jcoder
3
我的错,我混淆了动态分配和自动存储 :( 我刚刚查了一下规范。如果存在常数表达式,则它必须是整数常量表达式,并且其值必须大于零。使用“-pedantic”g ++标志会出现警告:“ISO C ++禁止大小为零的数组”。 - legends2k
显示剩余6条评论
4个回答

11
您可以使用malloc为对象分配内存,然后使用placement new在先前分配的内存中创建对象:
void* memory = malloc(sizeof(Foo) + sizeof(int) * size);
Foo* foo = new (memory) Foo(size, data);

注意,要销毁该对象时,不能使用delete命令。你需要手动调用析构函数,然后使用free命令释放用malloc命令分配的内存:
foo->~Foo();
free(memory); //or free(foo);

请注意,正如@Nikos C.@GManNickG所建议的那样,您可以使用::operator new以更C++的方式完成相同的操作:

void* memory = ::operator new(sizeof(Foo) + sizeof(int) * size);
Foo* foo = new (memory) Foo(size, data);
...
foo->~Foo();
::operator delete(memory); //or ::operator delete(foo);

3
最好使用malloc(sizeof(Foo) + ...)而不是sizeof(int),这样以后如果Foo再增加一个成员变量就会稍微更安全一些。 - user9876
2
你实际上可以避免使用malloc(),而是使用void* memory = new char[sizeof(Foo) + sizeof(int) * size]; - Nikos C.
1
如果您需要原始内存,请不要使用mallocnew char[],而是使用::operator new。它的唯一目的就是分配原始内存。 - GManNickG
使用 ::operator new 和定位 new 后,只需要 delete foo; 是有效的吗? - aschepler
1
@aschepler:不,你只能删除你“new”的东西。虽然它们可能共享词汇“new”,但它们是不同的东西。要撤消::operator new,请使用::operator delete - GManNickG
显示剩余7条评论

8
你有一个执行此操作的库,但没有提供工厂函数?真是可耻!无论如何,虽然zakinster的方法是正确的(我将直接调用operator new而不是new char的数组),但它也容易出错,所以你应该把它封装起来。
struct raw_delete {
  void operator ()(void* ptr) {
    ::operator delete(ptr);
  }
};

template <typename T>
struct destroy_and_delete {
  void operator ()(T* ptr) {
    if (ptr) {
      ptr->~T();
      ::operator delete(ptr);
    }
  }
};
template <typename T>
using dd_unique_ptr = std::unique_ptr<T, destroy_and_delete<T>>;

using FooUniquePtr = dd_unique_ptr<Foo>;

FooUniquePtr CreateFoo(int* data, int size) {
  std::unique_ptr<void, raw_delete> memory{
    ::operator new(sizeof(Foo) + size * sizeof(int))
  };
  Foo* result = new (memory.get()) Foo(size, data);
  memory.release();
  return FooUniquePtr{result};
}

是的,这里有一点额外的开销,但大部分东西都是可重复使用的。


1
我认为一个好的C++解决方案是使用new获取原始内存,然后使用放置new将类嵌入其中。
获取原始内存的方法如下:
Foo *f = static_cast<Foo *>(operator new(sizeof(Foo)); 

构建接收到的内存中的对象的工作方式如下:
new (f) Foo(size, data); // placement new

记住,这也意味着你必须手动清理该地区。
f->~Foo(); // call the destructor
operator delete(f); // free the memory again

我个人的看法是,在新编写的C++代码中使用mallocfree是不好的。


1
如果你真的想偷懒,只需使用std::vector<Foo>。它会占用更多空间(我认为是3个指针而不是1个),但你可以获得容器的所有好处,如果你知道它永远不会改变大小,那么实际上没有任何缺点。
鉴于你的定义,你的对象是可移动的,因此你可以安全地执行以下操作,以消除向量在初始填充期间的重新分配...
auto initialFooValue = Foo(0, 0)
auto fooContainer = std::vector<Foo>(size, initialFooValue);

int i = 0;
for (auto& moveFoo : whereverYouAreFillingFrom)
{
    fooContainer[i] = std::move(moveFoo);
    ++i;
}

由于std :: vector是连续的,因此如果您的对象是trivially-copyable,您也可以安全地将其直接复制到其中。


这是我想到的一个解决方案,但不幸的是出于性能原因,我被迫使用提供的库... - Fabien
1
std::vector 的内部内容在其自身内是连续的,但它与更大的结构体并不连续。 - Ben Voigt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接