37

I've been looking into this for the past few days, and so far I haven't really found anything convincing other than dogmatic arguments or appeals to tradition (i.e. "it's the C++ way!").

If I'm creating an array of objects, what is the compelling reason (other than ease) for using:

#define MY_ARRAY_SIZE 10

//  ...

my_object * my_array=new my_object [MY_ARRAY_SIZE];

for (int i=0;i<MY_ARRAY_SIZE;++i) my_array[i]=my_object(i);

over

#define MEMORY_ERROR -1
#define MY_ARRAY_SIZE 10

//  ...

my_object * my_array=(my_object *)malloc(sizeof(my_object)*MY_ARRAY_SIZE);
if (my_object==NULL) throw MEMORY_ERROR;

for (int i=0;i<MY_ARRAY_SIZE;++i) new (my_array+i) my_object (i);

As far as I can tell the latter is much more efficient than the former (since you don't initialize memory to some non-random value/call default constructors unnecessarily), and the only difference really is the fact that one you clean up with:

delete [] my_array;

and the other you clean up with:

for (int i=0;i<MY_ARRAY_SIZE;++i) my_array[i].~T();

free(my_array);

I'm out for a compelling reason. Appeals to the fact that it's C++ (not C) and therefore malloc and free shouldn't be used isn't -- as far as I can tell -- compelling as much as it is dogmatic. Is there something I'm missing that makes new [] superior to malloc?

I mean, as best I can tell, you can't even use new [] -- at all -- to make an array of things that don't have a default, parameterless constructor, whereas the malloc method can thusly be used.

BlueRaja - Danny Pflughoeft
  • 84,206
  • 33
  • 197
  • 283
  • 60
    Neither is idiomatic C++. Idiomatic C++ would use `std::vector` or `std::array`. – David Brown Jan 22 '12 at 07:23
  • 1
    The main reasons I use new is because it's easier to read and understand, as well as having less keys I need to type :P Program performance isn't always everything, sometimes readability/maintainability is more important (granted, this is somewhat of a weak argument because someone could argue that reading the malloc/free version isn't that much more difficult to understand, if at all). – helloworld922 Jan 22 '12 at 07:29
  • 3
    You should look at the code that is generated in case of new[] to find the difference and understand at what price you are getting efficiency. Though if number of elements is not too high and time is not critical premature optimization in price of less-readable code makes no sense, imho. The Main virtue of "C++" way is that most of developers that will skim through the source will immediately understand it's purpose, while "malloc" can be a bit confusing. Though your approach is interesting. – Volodymyr Rudyi Jan 22 '12 at 07:35
  • 2
    This questions comes off as, basically, why use C++ and clear, readable code when you can be more optimized, yet more redundant, more prone to error, more complicated, require more documentation, and write more code. Seriously, it handles type-safety, construction, and destruction for you. If you want, you can go ahead and handle it your self, that's why malloc and free are still there. Otherwise, use new and delete appropriately. Most of the time, you don't. Why write mote code? Why make it more complicated? Why make it harder to read? – Sion Sheevok Jan 22 '12 at 08:39
  • 1
    I'm making it "more complicated" in the interest of efficiency and in the interest on making it more portable. Not every class has a parameterless constructor (required to use `new []`), but every class has a constructor or a copy constructor (one of which is required to use `new (void *) T ()`. The reason to use C++ (over C) is its OO paradigm and how it allows for much more wholesome abstraction. Using `malloc` and `new (void *) T ()` doesn't detract from this, rather it makes it more flexible, which is a selling point over C# or Java. – Robert Allan Hennigan Leahy Jan 22 '12 at 08:43
  • 3
    There are cases where it might be justified. Still, isn't this needless overengineering (lots of classes have a parameterless constructor, and frequently you **know** you only care about a few classes that do) and premature optimization in 95% of all cases? –  Jan 22 '12 at 08:47
  • 5
    The point is, at what cost was your efficiency? By forsaking `new` and `delete` entirely, how much performance are you gaining, realistically, over how much readability you're losing and redundancy you're committing? Unnecessary initialization has *never* been the bottleneck of any program I have, or anyone I know has, worked on and experienced. It is an **exceedingly rare situation** where unnecessary default constructors calls has been an issue. In those **rare cases**, it was handled **for those cases** and **abstracted** away **safely**. – Sion Sheevok Jan 22 '12 at 09:07
  • 5
    @RobertAllanHenniganLeahy: I don't understand something. You've clearly already decided what you want to do. You've considered and rejected the entirely legitimate and reasonable arguments against it. So... what exactly is your question? Are you trying to conduct an argument in the guise of a question? Are you fishing for validation for your already established viewpoint? Because you're not likely to find it. – Nicol Bolas Jan 22 '12 at 09:08
  • 1
    "I mean, as best I can tell, you can't even use new [] -- at all -- to make an array of things that don't have a default, parameterless constructor, whereas the malloc method can thusly be used." But you can use `std::vector` for such things. Also, the **vast** majority of objects have a default constructor. And of those objects that do not, how many of them would you really want to allocate a C-style array of? I can't remember the last time I wanted to call `new[]` of any kind. – Nicol Bolas Jan 22 '12 at 09:10
  • 1
    @NicolBolas: I thought I might be missing something un-obvious but incredibly important (some functional ramification/difference). But the posts here have confirmed what I previously believed -- that `new []` and `delete []` **are** handy, but that my method is just as valid and it attains my ends more directly (for me, personally, in the use cases I'm considering). I'm not trying to argue, just was trying to make sure I wasn't missing something important before committing to this course-of-action, since -- as mentioned -- other sources I've found seem dogmatic, not informative. – Robert Allan Hennigan Leahy Jan 22 '12 at 09:13
  • 3
    The portable argument also doesn't make sense. If you're writing C++ code, it's C++ code. It is not C code. What is not portable about `new` and `delete`? It is a platform-independent language feature. The fact that it supposedly can't be used with parameter'd constructors? You can pass parameters to custom constructors with `new`. You mean non-portable as in it requires a default constructor? Not true. Even so, if you're using a type, it should have a well defined interface. Using templates? Your template parameter better adhere to **some** well defined interface. That's just proper design. – Sion Sheevok Jan 22 '12 at 09:14
  • 1
    @RobertAllanHenniganLeahy: How much more efficient do you mean by "much more efficient"? 50% faster? 2x? 10x? – Todd Lehman Jan 22 '12 at 09:15
  • 3
    @Robert: the important thing you've missed was the fact that, in your own words, C++ allows for much more wholesome abstraction. In this case, that abstraction already exists and is called `std::vector`. It gives you the advantages (real or imaginary) of both methods, *and more*. – R. Martinho Fernandes Jan 22 '12 at 09:15
  • @ToddLehman I usually just go for whatever is the more efficient way of doing things. I don't really find that I have a readability problem between the two, and a decent comment could clear matters up (or better yet, appropriately-named and concise functions). If I was working with a style guide that prohibited `malloc` obviously I wouldn't use it, but that's a different matter. Iterating over anything is something I try to avoid/consolidate in any situation possible. – Robert Allan Hennigan Leahy Jan 22 '12 at 09:23
  • @R.MartinhoFernandes Yes, and clearly if I were just making an array I would use `std::vector` -- why re-invent the wheel? In my case I'm just talking about the principle of `new []` vs `malloc` combined with placement new, I'm not talking about `std::vector` either directly or tangentially. This is a big problem I have with any community where questions are answered -- not just SO -- my question was not "_is there an existing class that does this_" it was "_why is `malloc` considered a faux pas in C++_". Recommending `std::vector` is not an answer. – Robert Allan Hennigan Leahy Jan 22 '12 at 09:25
  • @R.MartinhoFernandes Moreover (ran out of characters) using `std::vector` does not suffice when you want or need collections **with qualities that `std::vector` does not have**. – Robert Allan Hennigan Leahy Jan 22 '12 at 09:26
  • 2
    @Robert: If you are not making an array of objects, you should not have introduced your question with "If I'm creating an array of objects, (...)". – R. Martinho Fernandes Jan 22 '12 at 09:32
  • 1
    I don't think there's any good reason to use the more complicated and more efficient version with `malloc` unless you know for sure that it actually makes a difference in your actual program. If it's 100 times faster but only saves 1/100 second over the lifetime of the entire program, that's not a valid reason to choose it. IMHO. – Todd Lehman Jan 22 '12 at 09:33
  • @R.MartinhoFernandes well "_array_" has many different uses. I'm a C programmer who's introducing myself to C++ because it seems like C++ is more flexible with Win32API programming. Previously it was a back-and-forth between C and C#. "_Array_" to me therefore can mean "_a sequence of objects in memory_", which is what I was referring to above. Or it can "_array_" as in _System.Array_ (from .NET). I'm sorry if this poor use of nomenclature was confusing. I'm creating an area -- "_array_" -- to be the backing store for a custom collection I'm writing, and `std::vector` will not suffice. – Robert Allan Hennigan Leahy Jan 22 '12 at 09:41
  • 2
    @Robert: no problem. Unfortunately, we programmers overload words with different meanings far too often :( But now you understand why everyone was going on about `std::vector`. – R. Martinho Fernandes Jan 22 '12 at 09:45
  • @ToddLehman I try to make code maximally reusable, and therefore while a slight optimization might save 1/100th of a second over my program's lifetime, if I later decide to use it for something more intense, I'm left either re-writing it or writing something new, which I'd prefer to avoid. – Robert Allan Hennigan Leahy Jan 22 '12 at 09:46
  • @Robert: "I'm a C programmer who's introducing myself to C++ because it seems like C++ is more flexible with Win32API programming." Then you should introduce yourself to C++ as **C++**, not "C with Classes". This means you do things the C++ way, because that's the correct way to do them. As for "maximally reusable," I've never seen any such "maximally reusable" code, and your array initialization certainly doesn't qualify. Especially since it will have to be copy-and-pasted everywhere you create an array. Copy-and-paste coding is not what I would call "reuse". – Nicol Bolas Jan 22 '12 at 09:57
  • 2
    @NicolBolas I covered this above. I'm not using C++ as "_C with Classes_". I use the C++ features where they belong, but I'm not afraid to use my C knowledge where it belongs. Templates, classes, and functions combine to create "_maximally reusable_" code. In this case -- as I said above -- I'm creating a backing store for a collection for a use case wherein `std::vector` will not suffice. – Robert Allan Hennigan Leahy Jan 22 '12 at 10:13
  • 3
    @RobertAllanHenniganLeahy good job sticking to your guns here. C++ programmers can be exceedingly whiny about idioms but fact is that such things are mere opinion. It is not necessarily 'the C++ way'; if anything C++ gives you the power to get down and dirty with C, that's there for a reason. If people want so much, they can go wrap this functionality up in a idiomatic package; they should't cry because someone got performance they're too scared to manage. IMO C++ memory management libraries are in the state they're in because of dogma holding back progress. – That Realty Programmer Guy Apr 04 '14 at 04:46

11 Answers11

64

I'm out for a compelling reason.

It depends on how you define "compelling". Many of the arguments you have thus far rejected are certainly compelling to most C++ programmers, as your suggestion is not the standard way to allocate naked arrays in C++.

The simple fact is this: yes, you absolutely can do things the way you describe. There is no reason that what you are describing will not function.

But then again, you can have virtual functions in C. You can implement classes and inheritance in plain C, if you put the time and effort into it. Those are entirely functional as well.

Therefore, what matters is not whether something can work. But more on what the costs are. It's much more error prone to implement inheritance and virtual functions in C than C++. There are multiple ways to implement it in C, which leads to incompatible implementations. Whereas, because they're first-class language features of C++, it's highly unlikely that someone would manually implement what the language offers. Thus, everyone's inheritance and virtual functions can cooperate with the rules of C++.

The same goes for this. So what are the gains and the losses from manual malloc/free array management?

I can't say that any of what I'm about to say constitutes a "compelling reason" for you. I rather doubt it will, since you seem to have made up your mind. But for the record:

Performance

You claim the following:

As far as I can tell the latter is much more efficient than the former (since you don't initialize memory to some non-random value/call default constructors unnecessarily), and the only difference really is the fact that one you clean up with:

This statement suggests that the efficiency gain is primarily in the construction of the objects in question. That is, which constructors are called. The statement presupposes that you don't want to call the default constructor; that you use a default constructor just to create the array, then use the real initialization function to put the actual data into the object.

Well... what if that's not what you want to do? What if what you want to do is create an empty array, one that is default constructed? In this case, this advantage disappears entirely.

Fragility

Let's assume that each object in the array needs to have a specialized constructor or something called on it, such that initializing the array requires this sort of thing. But consider your destruction code:

for (int i=0;i<MY_ARRAY_SIZE;++i) my_array[i].~T();

For a simple case, this is fine. You have a macro or const variable that says how many objects you have. And you loop over each element to destroy the data. That's great for a simple example.

Now consider a real application, not an example. How many different places will you be creating an array in? Dozens? Hundreds? Each and every one will need to have its own for loop for initializing the array. Each and every one will need to have its own for loop for destroying the array.

Mis-type this even once, and you can corrupt memory. Or not delete something. Or any number of other horrible things.

And here's an important question: for a given array, where do you keep the size? Do you know how many items you allocated for every array that you create? Each array will probably have its own way of knowing how many items it stores. So each destructor loop will need to fetch this data properly. If it gets it wrong... boom.

And then we have exception safety, which is a whole new can of worms. If one of the constructors throws an exception, the previously constructed objects need to be destructed. Your code doesn't do that; it's not exception-safe.

Now, consider the alternative:

delete[] my_array;

This can't fail. It will always destroy every element. It tracks the size of the array, and it's exception-safe. So it is guaranteed to work. It can't not work (as long as you allocated it with new[]).

Of course, you could say that you could wrap the array in an object. That makes sense. You might even template the object on the type elements of the array. That way, all the desturctor code is the same. The size is contained in the object. And maybe, just maybe, you realize that the user should have some control over the particular way the memory is allocated, so that it's not just malloc/free.

Congratulations: you just re-invented std::vector.

Which is why many C++ programmers don't even type new[] anymore.

Flexibility

Your code uses malloc/free. But let's say I'm doing some profiling. And I realize that malloc/free for certain frequently created types is just too expensive. I create a special memory manager for them. But how to hook all of the array allocations to them?

Well, I have to search the codebase for any location where you create/destroy arrays of these types. And then I have to change their memory allocators accordingly. And then I have to continuously watch the codebase so that someone else doesn't change those allocators back or introduce new array code that uses different allocators.

If I were instead using new[]/delete[], I could use operator overloading. I simply provide an overload for operators new[] and delete[] for those types. No code has to change. It's much more difficult for someone to circumvent these overloads; they have to actively try to. And so forth.

So I get greater flexibility and reasonable assurance that my allocators will be used where they should be used.

Readability

Consider this:

my_object *my_array = new my_object[10];
for (int i=0; i<MY_ARRAY_SIZE; ++i)
  my_array[i]=my_object(i);

//... Do stuff with the array

delete [] my_array;

Compare it to this:

my_object *my_array = (my_object *)malloc(sizeof(my_object) * MY_ARRAY_SIZE);
if(my_object==NULL)
  throw MEMORY_ERROR;

int i;
try
{
    for(i=0; i<MY_ARRAY_SIZE; ++i)
      new(my_array+i) my_object(i);
}
catch(...)  //Exception safety.
{
    for(i; i>0; --i)  //The i-th object was not successfully constructed
        my_array[i-1].~T();
    throw;
}

//... Do stuff with the array

for(int i=MY_ARRAY_SIZE; i>=0; --i)
  my_array[i].~T();
free(my_array);

Objectively speaking, which one of these is easier to read and understand what's going on?

Just look at this statement: (my_object *)malloc(sizeof(my_object) * MY_ARRAY_SIZE). This is a very low level thing. You're not allocating an array of anything; you're allocating a hunk of memory. You have to manually compute the size of the hunk of memory to match the size of the object * the number of objects you want. It even features a cast.

By contrast, new my_object[10] tells the story. new is the C++ keyword for "create instances of types". my_object[10] is a 10 element array of my_object type. It's simple, obvious, and intuitive. There's no casting, no computing of byte sizes, nothing.

The malloc method requires learning how to use malloc idiomatically. The new method requires just understanding how new works. It's much less verbose and much more obvious what's going on.

Furthermore, after the malloc statement, you do not in fact have an array of objects. malloc simply returns a block of memory that you have told the C++ compiler to pretend is a pointer to an object (with a cast). It isn't an array of objects, because objects in C++ have lifetimes. And an object's lifetime does not begin until it is constructed. Nothing in that memory has had a constructor called on it yet, and therefore there are no living objects in it.

my_array at that point is not an array; it's just a block of memory. It doesn't become an array of my_objects until you construct them in the next step. This is incredibly unintuitive to a new programmer; it takes a seasoned C++ hand (one who probably learned from C) to know that those aren't live objects and should be treated with care. The pointer does not yet behave like a proper my_object*, because it doesn't point to any my_objects yet.

By contrast, you do have living objects in the new[] case. The objects have been constructed; they are live and fully-formed. You can use this pointer just like any other my_object*.

Fin

None of the above says that this mechanism isn't potentially useful in the right circumstances. But it's one thing to acknowledge the utility of something in certain circumstances. It's quite another to say that it should be the default way of doing things.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 4
    Given the excellent statement here, I'd rather add to your answer than writing one of my own. You have missed one excellent reason: exception safety. If the call to the 3rd constructor fails, the destructor should be called for the two first constructed objects. `new[]` does this automatically, the OP's solution is buggy. – Matthieu M. Jan 22 '12 at 12:02
  • 1
    @MatthieuM.: I've added a bit about exception safety into the Fragility section. Thanks for the reminder. – Nicol Bolas Jan 22 '12 at 16:52
  • Although the downside of `new` and by extension `new[]` is that people can throw exceptions in their constructors. Which seems okay at first until you try and allocate `new obj[10]` and at #9 the `obj()` constructor throws you an exception. You will have a lot of fun trying to avoid leaking the previous 9 successfully created objects. – user268396 Jan 22 '12 at 17:15
  • 5
    @user268396: What are you talking about? The C++ specification is very clear on this: if an object throws in its constructor, all previously constructed objects will be destroyed. This is *automatic* and *required* by the specification. If you've seen this not happen, that's a compiler bug. – Nicol Bolas Jan 22 '12 at 17:16
  • Why use `new` or `malloc` at all in a modern c++ application? `vector`+`reserve` is better than both in maintainability, safety, and performance. – Inverse Jan 28 '12 at 21:25
  • 1
    @Inverse: Anyone who's willing to use `malloc` over `new[]` is probably too invested in the C way of doing things to consider `vector`. Also, I mentioned that all of the steps one would need to take to make `malloc/free` actually work (storing the length so that the right number of destructors would be called, exception safety, etc) would effectively give you `vector`. – Nicol Bolas Jan 28 '12 at 21:48
38

If you do not want to get your memory initialized by implicit constructor calls, and just need an assured memory allocation for placement new then it is perfectly fine to use malloc and free instead of new[] and delete[].

The compelling reasons of using new over malloc is that new provides implicit initialization through constructor calls, saving you additional memset or related function calls post an malloc And that for new you do not need to check for NULL after every allocation, just enclosing exception handlers will do the job saving you redundant error checking unlike malloc.
These both compelling reasons do not apply to your usage.

which one is performance efficient can only be determined by profiling, there is nothing wrong in the approach you have now. On a side note I don't see a compelling reason as to why use malloc over new[] either.

Alok Save
  • 202,538
  • 53
  • 430
  • 533
  • 4
    @Als: The compelling reason for using `new[]` over the OP's solution is **Exception Safety**. The OP code will either crash or leak in case of exception thrown during the construction, `new[]` will let the exception propagate *after cleaning up*. – Matthieu M. Jan 22 '12 at 12:09
  • @MatthieuM.: I believe the OP is already doing the error handling by checking return values of `malloc`, it is no doubt cumbersome, but possible.Just since I noticed so much traffic on this thread, let me be clear.From the way the OP framed the Q,My impression was & still is that OP is not a new user who is taking a route for lack of knowing the right one,but is an pretty experienced user who understands the semantics and is expecting a direct answer.I just provided that here,Yes there are better ways of doing it but alternate ways not what the OP was asking actually. – Alok Save Jan 22 '12 at 13:08
  • 6
    @Als: I believe the OP is experienced (in C at least), however his solution is incorrect in the face of exceptions. What happens if the initialization of the 3rd object throws ? Who cleans up the 2 objects previously built ? How does he ensures that the destructor is not called on uninitialized raw memory ? `new[]` handles it, the OP does not, it's not a full fledged replacement, just a shaky one. – Matthieu M. Jan 22 '12 at 13:59
  • 1
    @Robert Allan Hennigan Leahy: an important part of answering questions is knowing when a person is asking the wrong question. If someone said "I need some money: should I rob a bank or go to a loan shark?" I would rightly advise *neither* alternative, and suggest things like getting a loan from a bank or borrowing from friends -- or to help them realize that they don't even need the money at all. 99.9% of people who ask questions yours fall into those categories; people choosing between two bad alternatives, or even asking questions about a topic they shouldn't be worrying about. –  Jan 22 '12 at 17:19
19

I would say neither.

The best way to do it would be:

std::vector<my_object>   my_array;
my_array.reserve(MY_ARRAY_SIZE);

for (int i=0;i<MY_ARRAY_SIZE;++i)
{    my_array.push_back(my_object(i));
}

This is because internally vector is probably doing the placement new for you. It also managing all the other problems associated with memory management that you are not taking into account.

Martin York
  • 257,169
  • 86
  • 333
  • 562
10

You've reimplemented new[]/delete[] here, and what you have written is pretty common in developing specialized allocators.

The overhead of calling simple constructors will take little time compared the allocation. It's not necessarily 'much more efficient' -- it depends on the complexity of the default constructor, and of operator=.

One nice thing that has not been mentioned yet is that the array's size is known by new[]/delete[]. delete[] just does the right and destructs all elements when asked. Dragging an additional variable (or three) around so you exactly how to destroy the array is a pain. A dedicated collection type would be a fine alternative, however.

new[]/delete[] are preferable for convenience. They introduce little overhead, and could save you from a lot of silly errors. Are you compelled enough to take away this functionality and use a collection/container everywhere to support your custom construction? I've implemented this allocator -- the real mess is creating functors for all the construction variations you need in practice. At any rate, you often have a more exact execution at the expense of a program which is often more difficult to maintain than the idioms everybody knows.

justin
  • 104,054
  • 14
  • 179
  • 226
7

IMHO there both ugly, it's better to use vectors. Just make sure to allocate the space in advance for performance.

Either:

std::vector<my_object> my_array(MY_ARRAY_SIZE);

If you want to initialize with a default value for all entries.

my_object basic;
std::vector<my_object> my_array(MY_ARRAY_SIZE, basic);

Or if you don't want to construct the objects but do want to reserve the space:

std::vector<my_object> my_array;
my_array.reserve(MY_ARRAY_SIZE);

Then if you need to access it as a C-Style pointer array just (just make sure you don't add stuff while keeping the old pointer but you couldn't do that with regular c-style arrays anyway.)

my_object* carray = &my_array[0];      
my_object* carray = &my_array.front(); // Or the C++ way

Access individual elements:

my_object value = my_array[i];    // The non-safe c-like faster way
my_object value = my_array.at(i); // With bounds checking, throws range exception

Typedef for pretty:

typedef std::vector<my_object> object_vect;

Pass them around functions with references:

void some_function(const object_vect& my_array);

EDIT: IN C++11 there is also std::array. The problem with it though is it's size is done via a template so you can't make different sized ones at runtime and you cant pass it into functions unless they are expecting that exact same size (or are template functions themselves). But it can be useful for things like buffers.

std::array<int, 1024> my_array;

EDIT2: Also in C++11 there is a new emplace_back as an alternative to push_back. This basically allows you to 'move' your object (or construct your object directly in the vector) and saves you a copy.

std::vector<SomeClass> v;
SomeClass bob {"Bob", "Ross", 10.34f};
v.emplace_back(bob);
v.emplace_back("Another", "One", 111.0f); // <- Note this doesn't work with initialization lists ☹
David C. Bishop
  • 6,437
  • 3
  • 28
  • 22
  • 1
    +1, but you can use function templates for functions that take arrays, such as in [this `swap` function](http://msdn.microsoft.com/en-us/library/bb982603.aspx) – Felix Dombek Jan 22 '12 at 08:04
  • 6
    I want to upvote this answer, but `&my_array[0]` exactly the same as `&my_array.front()`. In both cases, behaviour is undefined if the vector is empty. Also, throwing an exception on out-of-range doesn't make an incorrect program any more correct. – Mankarse Jan 22 '12 at 08:42
  • 1
    @David: I tried editing this, but the edit was rejected because it wasn't enough characters changed. Anyway, line 8 is missing a carriage return at the end of it, which is causing the block of code to be rendered as paragraph text instead of code. – Todd Lehman Jan 22 '12 at 09:22
  • @Mankarse: Fixed the front() comment. Throwing an out of range exception is much better than just continuing on with corrupt data (even if it's just a different way of crashing). – David C. Bishop Jan 22 '12 at 15:29
  • directly accessing elements in an empty vector even after calling reserve is unfortunately still undefined behavior. – rubenvb Apr 19 '13 at 21:35
  • @rubenvb: As it should be. Not even sure why you would you even want to access them. What happens if they are complex types with constructors and so on. Makes more sense to use resize instead... Also with C++11 there is emplace_back which uses std::move to basically allow you to construct your new object directly in the vector. – David C. Bishop Apr 20 '13 at 02:10
  • It makes it impossible to use it as a dynamic buffer (for calls into C functions), although we do have unique_ptr arrays for that, but you'd have to call new[] explicitly... – rubenvb Apr 20 '13 at 06:37
5

Oh well, I was thinking that given the number of answers there would be no reason to step in... but I guess I am drawn in as the others. Let's go

  1. Why your solution is broken
  2. C++11 new facilities for handling raw memory
  3. Simpler way to get this done
  4. Advices

1. Why your solution is broken

First, the two snippets you presented are not equivalent. new[] just works, yours fails horribly in the presence of Exceptions.

What new[] does under the cover is that it keeps track of the number of objects that were constructed, so that if an exception occurs during say the 3rd constructor call it properly calls the destructor for the 2 already constructed objects.

Your solution however fails horribly:

  • either you don't handle exceptions at all (and leak horribly)
  • or you just try to call the destructors on the whole array even though it's half built (likely crashing, but who knows with undefined behavior)

So the two are clearly not equivalent. Yours is broken

2. C++11 new facilities for handling raw memory

In C++11, the comittee members have realized how much we liked fiddling with raw memory and they have introduced facilities to help us doing so more efficiently, and more safely.

Check cppreference's <memory> brief. This example shows off the new goodies (*):

#include <iostream>
#include <string>
#include <memory>
#include <algorithm>

int main()
{
    const std::string s[] = {"This", "is", "a", "test", "."};
    std::string* p = std::get_temporary_buffer<std::string>(5).first;

    std::copy(std::begin(s), std::end(s),
              std::raw_storage_iterator<std::string*, std::string>(p));

    for(std::string* i = p; i!=p+5; ++i) {
        std::cout << *i << '\n';
        i->~basic_string<char>();
    }
    std::return_temporary_buffer(p);
}

Note that get_temporary_buffer is no-throw, it returns the number of elements for which memory has actually been allocated as a second member of the pair (thus the .first to get the pointer).

(*) Or perhaps not so new as MooingDuck remarked.

3. Simpler way to get this done

As far as I am concered, what you really seem to be asking for is a kind of typed memory pool, where some emplacements could not have been initialized.

Do you know about boost::optional ?

It is basically an area of raw memory that can fit one item of a given type (template parameter) but defaults with having nothing in instead. It has a similar interface to a pointer and let you query whether or not the memory is actually occupied. Finally, using the In-Place Factories you can safely use it without copying objects if it is a concern.

Well, your use case really looks like a std::vector< boost::optional<T> > to me (or perhaps a deque?)

4. Advices

Finally, in case you really want to do it on your own, whether for learning or because no STL container really suits you, I do suggest you wrap this up in an object to avoid the code sprawling all over the place.

Don't forget: Don't Repeat Yourself!

With an object (templated) you can capture the essence of your design in one single place, and then reuse it everywhere.

And of course, why not take advantage of the new C++11 facilities while doing so :) ?

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • MSVC has had `get_temporary_buffer` forever, is that really not part of C++03? Also, something to mention is `void* memory = operator new(sizeof(my_object)*10);` – Mooing Duck Jan 22 '12 at 17:58
  • @MooingDuck: I would have thought not, but I don't have a standard to check so you may well be right (looking at my own link I realize it was not tagged C++11). – Matthieu M. Jan 23 '12 at 07:43
  • 1
    Note that `get_temporary_buffer` and `raw_storage_iterator` are now deprecated in C++17. Probably we should do something like `std::pmr::monotonic_buffer_resource resource(sizeof(std::string)*5); std::pmr::polymorphic_allocator pallocator(&resource); std::string* p = pallocator.allocate(5);` Although no compilers support it yet. It came from Boost, though, so I wrote up [a demo using that](https://wandbox.org/permlink/Kah6gXu33OYTtbzL) – AndyG Jun 01 '17 at 12:23
3

You should use vectors.

Mateen Ulhaq
  • 24,552
  • 19
  • 101
  • 135
ddacot
  • 1,212
  • 3
  • 14
  • 40
2

Dogmatic or not, that is exactly what ALL the STL container do to allocate and initialize.

They use an allocator then allocates uninitialized space and initialize it by means of the container constructors.

If this (like many people use to say) "is not c++" how can be the standard library just be implemented like that?

If you just don't want to use malloc / free, you can allocate "bytes" with just new char[]

myobjet* pvext = reinterpret_cast<myobject*>(new char[sizeof(myobject)*vectsize]);
for(int i=0; i<vectsize; ++i) new(myobject+i)myobject(params);
...
for(int i=vectsize-1; i!=0u-1; --i) (myobject+i)->~myobject();
delete[] reinterpret_cast<char*>(myobject);

This lets you take advantage of the separation between initialization and allocation, still taking adwantage of the new allocation exception mechanism.

Note that, putting my first and last line into an myallocator<myobject> class and the second ands second-last into a myvector<myobject> class, we have ... just reimplemented std::vector<myobject, std::allocator<myobject> >

Emilio Garavaglia
  • 20,229
  • 2
  • 46
  • 63
  • 2
    "If this (like many people use to say) "is not c++" how can be the standard library just be implemented like that?" Because it's wrapped up in a library with a specialized container, who's constructors and destructor make it all invisible and transparent. Plus, these standard containers actually use this feature, because allocators allow the replacement of the memory allocation part. Whereas calling `malloc` will always call `malloc`; `malloc` is not extensible or overridable. – Nicol Bolas Jan 22 '12 at 09:13
  • @NicolBolas: True, but: May be the OP is in the same situation. If this is valid for the STL author, it must be valid for the OP as well. May be it is not "convenient", but convenience is not morality and morality is not legality. What I was saying is that -often- a "moral" problem is dispatched as a "legal" one. That's -to me- it's wrong. – Emilio Garavaglia Jan 22 '12 at 09:28
1

What you have shown here is actually the way to go when using a memory allocator different than the system general allocator - in that case you would allocate your memory using the allocator (alloc->malloc(sizeof(my_object))) and then use the placement new operator to initialize it. This has many advantages in efficient memory management and quite common in the standard template library.

smichak
  • 4,716
  • 3
  • 35
  • 47
1

If you are writing a class that mimics functionality of std::vector or needs control over memory allocation/object creation (insertion in array / deletion etc.) - that's the way to go. In this case, it's not a question of "not calling default constructor". It becomes a question of being able to "allocate raw memory, memmove old objects there and then create new objects at the olds' addresses", question of being able to use some form of realloc and so on. Unquestionably, custom allocation + placement new are way more flexible... I know, I'm a bit drunk, but std::vector is for sissies... About efficiency - one can write their own version of std::vector that will be AT LEAST as fast ( and most likely smaller, in terms of sizeof() ) with most used 80% of std::vector functionality in, probably, less than 3 hours.

lapk
  • 3,838
  • 1
  • 23
  • 28
  • 3
    You're definitively drunk 8-) Frankly, if I have to spend 3h each time I write a C++ program, I'd waste a lot of my time... Now I don't disagree that once in a while rewriting an std::vector or alike container can be useful (i.e. for a realtime system) in most cases the default std::vector is much easier and compatible with boost::shared_ptr<>! – Alexis Wilke Jan 22 '12 at 09:24
  • @Azza: sure one can write a faster vector. That's easy. Until you remember exception safety. Then it's _really_ hart to match vector. – Mooing Duck Jan 22 '12 at 17:57
  • @AlexisWilke I'm not drunk anymore, although, I have a slight hangover now... You spend 3 hours writing a templated `vector` and then you use it in all your programs. Why would you re-write it every time? – lapk Jan 23 '12 at 01:05
  • @MooingDuck Exceptions are integral part of the C++. Or so they should be treated: RAII + exceptions = all your error handling, or close to it. I don't see what's so hard to match there, it's normal to use exceptions in C++, there is nothing new about it. – lapk Jan 23 '12 at 01:07
  • Actually the 3 hours each time is necessary if you work with a different customer each time. Unless you do that in your free time and license it so it can be reused by anyone... My main problem with that is that my version is likely to only work with g++. – Alexis Wilke Jan 24 '12 at 09:26
0
my_object * my_array=new my_object [10];

This will be an array with objects.

my_object * my_array=(my_object *)malloc(sizeof(my_object)*MY_ARRAY_SIZE);

This will be an array the size of your objects, but they may be "broken". If your class has virtual funcitons for instance, then you won't be able to call those. Note that it's not just your member data that may be inconsistent, but the entire object is actully "broken" (in lack of a better word)

I'm not saying it's wrong to do the second one, just as long as you know this.

user1130005
  • 392
  • 1
  • 4
  • 15
  • Except the objects aren't broken, that's the point, that's where the efficiency comes from. `new []` sets up the memory it allocates to not be "broken", but I'm never going to use those objects anyway, I'm just going to put new objects in there. If I were to use `=` to put objects in there, it'd be a problem, because then you'd be calling the `operator =` of a broken object, but I use `new (void *) T ()` to just overwrite the memory with a brand new object, so that's not an issue either. – Robert Allan Hennigan Leahy Jan 22 '12 at 08:15
  • As for virtual function calls, the `sizeof(T)` includes the pointer to the object's vtable, and that is setup during construction (which happens because `new (void *) T ()` calls the object's constructor). This means that the object that I'll actually use -- the one I make with placement new -- will have functional virtual calls. [See here for an example that proves this.](http://pastebin.com/YSgbQUPw) – Robert Allan Hennigan Leahy Jan 22 '12 at 08:16
  • 1
    Yes, as I said, it's not wrong to do what you do. Just something one need to keep in mind. – user1130005 Jan 22 '12 at 08:25
  • 1
    @RobertAllanHenniganLeahy: His point is that your `my_object * my_array` statement is a deception. `my_array` does not *yet* hold `my_object` types, because they have not yet been constructed. It's a pointer to valid memory, but the memory does not contain a live object yet. Yes, you will construct them right afterwards, but until you do, the memory doesn't contain live objects. – Nicol Bolas Jan 22 '12 at 09:15