Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ New vs Malloc for dynamic memory array of Objects

I have a class Bullet that takes several arguments for its construction. However, I am using a dynamic memory array to store them. I am using C++ so i want to conform to it's standard by using the new operator to allocate the memory. The problem is that the new operator is asking for the constructor arguments when I'm allocating the array, which I don't have at that time. I can accomplish this using malloc to get the right size then fill in form there, but that's not what i want to use :) any ideas?

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

Thanks.

like image 762
Trent Avatar asked Dec 05 '25 22:12

Trent


2 Answers

You can't.

And if you truly want to conform to C++ standards, you should use std::vector.

FYI, it would probably be even more expensive than what you're trying to achieve. If you could do this, new would call a constructor. But since you'll modify the object later on anyway, the initial construction is useless.

like image 154
Luchian Grigore Avatar answered Dec 07 '25 17:12

Luchian Grigore


1) std::vector

A std::vector really is the proper C++ way to do this.

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 [] operator

It is also possible to do this with new, but you really shouldn't. Manually managing resources with new/delete is an advanced task, similar to template meta-programming in that it's best left to library builders, who'll use these features to build efficient, high level libraries for you. In fact to do this correctly you'll basically be implementing the internals of std::vector.

When you use the new operator to allocate an array, every element in the array is default initialized. Your code could work if you added a default constructor to Bullet:

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

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

Then, when you have the real data for a Bullet you can assign it to one of the elements of the array:

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

Note the use of unique_ptr to ensure that proper clean-up occurs, and that it's exception safe. Doing these things manually is difficult and error prone.


3) operator new

The new operator initializes its objects in addition to allocating space for them. If you want to simply allocate space, you can use operator new.

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

(Note that the unique_ptr ensures that the storage will be deallocated but no more. Specifically, if we construct any objects in this storage we have to manually destruct them and do so in an exception safe way.)

bullets now points to storage sufficient for an array of Bullets. You can construct an array in this storage:

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

However the array construction again uses default initialization for each element, which we're trying to avoid.

AFAIK C++ doesn't specify any well defined method of constructing an array without constructing the elements. I imagine this is largely because doing so would be a no-op for most (all?) C++ implementations. So while the following is technically undefined, in practice it's pretty well defined.

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(...) {}
}

That will construct elements of the array without using the default constructor. You don't have to construct every element, just the ones you want to use. However when destroying the elements you have to remember to destroy only the elements that were constructed.

// 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 

The above is a pretty simple case. Making non-trivial uses of this method exception safe without wrapping it all up in a class is more difficult. Wrapping it up in a class basically amounts to implementing std::vector.


4) std::vector

So just use std::vector.

like image 20
bames53 Avatar answered Dec 07 '25 17:12

bames53