C++ New vs Malloc 用于对象的动态内存数组

Posted

技术标签:

【中文标题】C++ New vs Malloc 用于对象的动态内存数组【英文标题】:C++ New vs Malloc for dynamic memory array of Objects 【发布时间】:2012-03-31 01:26:40 【问题描述】:

我有一个类 Bullet,它的构造需要几个参数。但是,我使用动态内存数组来存储它们。我正在使用 C++,所以我想通过使用 new 运算符来分配内存来符合它的标准。问题是 new 运算符在我分配数组时要求构造函数参数,而当时我没有。我可以使用 malloc 来获得正确的大小,然后在那里填写表格,但这不是我想要使用的:) 有什么想法吗?

pBulletArray = (Bullet*) malloc(iBulletArraySize * sizeof(Bullet)); // Works
pBulletArray = new Bullet[iBulletArraySize]; // Requires constructor arguments

谢谢。

【问题讨论】:

为什么不等到您准备好构造对象?在您构建 Bullet 之前,您的 Bullet* 没有可指向的 Bullet 我不想每秒请求新内存 20 次。 调用构造函数时如何影响必须分配内存的次数?听起来您的问题背后有一些不正确的假设。 (此外,每秒 20 次分配在现代 PC 上不算什么。每秒数千次分配是现代软件的典型特征。考虑在浏览器上打开这样的网页!) @DavidSchwartz - 我认为 OP 正在创建一个子弹池(杂志?),以允许重复使用子弹,从而避免在应用程序运行期间调用内存管理。好的,每秒 20 次调用并不算多,但也许内存管理器已经很重了,(这不仅仅是在 mm 调用中花费的时间,还有任何时间浪费在锁争用上)。此外,除了内存分配之外,构造本身在其他方面可能会很昂贵(尽管这里似乎不是这种情况)。 @Trent:在你准备好使用它们之前,你必须把它们放在某个地方,对吧?那么为什么不让分配器这样做,因为它的目的是保留未使用的内存,直到您准备好使用它。这听起来像是一个非常复杂的非问题解决方案。当你需要内存时,分配它。完成后,释放它。您必须在某个地方进行跟踪,为什么不使用专门用于此目的的设备? 【参考方案1】:

你不能。

如果你真的想符合 C++ 标准,你应该使用std::vector

仅供参考,它可能会比您想要实现的目标更昂贵。如果你能做到这一点,new 会调用一个构造函数。但是由于无论如何您稍后都会修改对象,因此初始构造是无用的。

【讨论】:

我给子弹所有的属性(位置,航向等),当它被发射时,我现在所做的就是为要放入的子弹设置一些内存。 所以这种新的东西是不可能的? 感谢您的帮助,我正在避免使用向量,我将使用 malloc 来完成。 @Trent 好的。我编辑了我的答案,你可能不会想要那个。 我想不可能为分配目的提供默认 ctor 并通过赋值(RVO;请参阅***.com/q/2323225/1214731 的答案)构建子弹?【参考方案2】:

1) std::vector

std::vector 确实是执行此操作的正确 C++ 方式。

std::vector<Bullet> bullets;
bullets.reserve(10); // allocate memory for bullets without constructing any

bullets.push_back(Bullet(10.2,"Bang")); // put a Bullet in the vector.
bullets.emplace_back(10.2,"Bang"); // (C++11 only) construct a Bullet in the vector without copying. 

2) new [] 运营商

也可以使用new 执行此操作,但您确实不应该这样做。使用new/delete 手动管理资源是一项高级任务,类似于模板元编程,因为它最好留给库构建者,他们将使用这些功能为您构建高效的高级库。实际上,要正确执行此操作,您基本上将实现 std::vector 的内部结构。

当您使用new 运算符分配数组时,数组中的每个元素都默认初始化。如果您将默认构造函数添加到 Bullet,您的代码就可以工作:

class Bullet 
public:
    Bullet()  // default constructor
    Bullet(double,std::string const &) 
;

std::unique_ptr<Bullet[]> b = new Bullet[10]; // default construct 10 bullets

然后,当您拥有 Bullet 的真实数据时,您可以将其分配给数组的元素之一:

b[3] = Bullet(20.3,"Bang");

注意使用unique_ptr 以确保进行适当的清理,并且它是异常安全的。手动执行这些操作既困难又容易出错。


3) operator new

new 运算符除了为它们分配空间外,还初始化其对象。如果你想简单地分配空间,你可以使用operator new

std::unique_ptr<Bullet,void(*)(Bullet*)> bullets(
    static_cast<Bullet*>(::operator new(10 * sizeof(Bullet))),
    [](Bullet *b)::operator delete(b););

(请注意,unique_ptr 确保存储将被释放,但不会再释放。具体来说,如果我们在此存储中构造任何对象,我们必须手动销毁它们,并以异常安全的方式这样做。)

bullets 现在指向足以容纳Bullets 数组的存储空间。你可以在这个存储中构造一个数组:

new (bullets.get()) Bullet[10];

然而,数组构造再次对每个元素使用默认初始化,这是我们试图避免的。

AFAIK C++ 没有指定任何定义明确的构造数组而不构造元素的方法。我想这主要是因为这样做对于大多数(全部?)C++ 实现来说都是无操作的。因此,虽然以下内容在技术上是未定义的,但在实践中它的定义非常好。

bool constructed[10] = ; // a place to mark which elements are constructed

// construct some elements of the array
for(int i=0;i<10;i+=2) 
    try 
        // pretend bullets points to the first element of a valid array. Otherwise 'bullets.get()+i' is undefined
        new (bullets.get()+i) Bullet(10.2,"Bang");
        constructed = true;
     catch(...) 

这将在不使用默认构造函数的情况下构造数组元素。您不必构建每个元素,只需构建您想要使用的元素。但是,在销毁元素时,您必须记住只销毁已构建的元素。

// destruct the elements of the array that we constructed before
for(int i=0;i<10;++i) 
    if(constructed[i]) 
        bullets[i].~Bullet();
    


// unique_ptr destructor will take care of deallocating the storage 

以上是一个非常简单的案例。在不将其全部包装在一个类中的情况下,使这种方法异常的非平凡使用变得安全更加困难。将其封装在一个类中基本上相当于实现std::vector


4) std::vector

所以只需使用std::vector

【讨论】:

【参考方案3】:

可能做你想做的事——如果你真的想知道怎么做,请搜索“operator new”。但这几乎可以肯定是个坏主意。相反,请使用 std::vector,它将为您处理所有烦人的细节。您可以使用 std::vector::reserve 提前分配您将使用的所有内存。

【讨论】:

【参考方案4】:
Bullet** pBulletArray = new Bullet*[iBulletArraySize];

然后填充 pBulletArray:

for(int i = 0; i < iBulletArraySize; i++)

   pBulletArray[i] = new Bullet(arg0, arg1);

不要忘记之后使用 delete 来释放内存。

【讨论】:

@Jesse 我认为真正的问题是“他理解这个问题吗?”【参考方案5】:

C++ new 通常的工作方式是为类实例分配内存,然后为该实例调用构造函数。您基本上已经为您的实例分配了内存。

您只能像这样调用第一个实例的构造函数:

new((void*)pBulletArray) Bullet(int foo);

调用第二个的构造函数看起来像这样(等等)

new((void*)pBulletArray+1) Bullet(int bar);

如果 Bullet 构造函数采用 int。

【讨论】:

【参考方案6】:

如果您在这里真正追求的只是快速分配/释放,那么您应该研究“内存池”。我建议使用boost's implementation,而不是尝试自己动手。特别是,您可能希望使用“object_pool”。

【讨论】:

以上是关于C++ New vs Malloc 用于对象的动态内存数组的主要内容,如果未能解决你的问题,请参考以下文章

C++中的new VS C语言中的malloc

C++面试宝典

C++面试题

new和malloc的区别

malloc和new的区别

C++常见面试题30道