views:

442

answers:

8

There was an article i found long ago (i cant find it ATM) which states reasons why the new keyword in C++ is bad. I cant remember all of the reasons but the two i remember most is you must match new with delete, new[] with delete[] and you cannot use #define with new as you could with malloc.

I am designing a language so i like to ask how would you change the C++ language so new is more friendly. Feel free to state problems with new and articles. I wish i can find the article link but i remember it was long and was written by a professor at (IIRC) a known school.

A: 

Use Garbage Collection so that you never need to match new with anything.

djna
Agree. C++ uses a very versatile garbage collection mechanism called smart pointers.
Martin York
Hear, hear! RAII is waaaaayyyy more powerful than non-deterministic GC!
Drew Hall
*But* smart pointers are enormously slower than modern garbage collectors.
280Z28
And smart pointers don't do the same job - freeing cycles - and usually require locking ( at at least the bus level ) for each acquire or release, rather than the coarse locking required by gc.
Pete Kirkham
it's a valid point. I like RAII as much as anyone, but the claim that it completely replaces a GC is just silly. A GC would be more efficient, and would handly cycles. The downside is that you lose deterministic destruction. I don't see why a language shouldn't be able to offer both. Let me choose, when defining a type, how it should be destructed - GC or deterministic.
jalf
I've got a proof - that won't fit in the margin here - showing GCs are necessarily no more efficient than RAII, because for every GC implementation there is a trivial RAII implementation on top of that.
MSalters
Why the downvote? Really helps if you tell. GC may or not be the best, but is it intrinsically bad?
djna
+2  A: 

I'd give it the semantics of new in C# (more or less):

  1. Allocates memory for the object.
  2. Initializes the memory by setting the member variables to their default values (generally 0 for values, null for references).
  3. Initializes the object's dynamic binding mechanism (vtables in C++, type def tables for managed VMs).
  4. Calls the constructor, at which point virtual calls work as expected.
  5. For a language without garbage collection (eww for a new language at this point), return a smart_ptr or similar from the call.

Also, make all objects either value types or reference types, so you don't have to keep an explicit smart_ptr. Only allow new to heap-allocate for reference types, and make sure it contains information to properly call the destructor. For value types, new calls the constructor on memory from the stack.

280Z28
I dunno, I like that way D does it. Garbage collection, but the ability to do RAII.
GMan
Between `try`/`finally` and `using`, I never feel like I'm missing anything.
280Z28
That presumes that all value types are "small" (i.e. fit on the stack). I would submit that huge matrices are value types but certainly don't belong on the stack.
Drew Hall
@Drew: Not really the thread for details, but I'm actually worked on some interesting (to me at least) cooperation between matrices and the GC/JIT to meet C/C++ speeds in a managed VM for scientific computing. If I wasn't tied down here for school, it's probably the area I'd be trying to get a job. :D
280Z28
Me, I'm missing `using` for composed subobjects (i.e. for member fields).
Pavel Minaev
@Pavel, that's never a problem because every class with `IDisposable` fields implements `IDisposable`.
280Z28
You miss the point - when I _write_ a class with `IDisposaable` fields, I have to implement `IDisposable` myself. I do not want to do that - I just want to say "these here fields `Foo` and `Bar` are owned by me, so make me `IDisposable`, and when someone calls `Dispose` on me, delegate to them", complete with support for the usual C# `IDisposable` pattern (with protected method with `bool` etc). In other words, precisely how C++/CLI does that already.
Pavel Minaev
Clarification of my earlier comment: Huge matrix OBJECTS can/should be on the stack, but their DATA clearly should not be. That was bothering me...
Drew Hall
+6  A: 

I cannot see any reason to replace the new keyword with something else (and seems to be that C++ committee agree with me). It is clear and makes what it should. You could override operator new in your class, no need to use defines.

To eliminate new[]/delete[] problem you could use std::vector.

If you want to use smart pointer you could use it, but I want to control when smart pointer will be used. That's why I like how it works in C++ — high level behavior with ability to control low level details.

Kirill V. Lyadvinsky
Agreed. Instead I'd replace the new keyword in Java, since you can't put an object on the stack anyway!
Drew Hall
Note that `boost/tr1/std::array` is not/will not be a replacement for `new[]`/`delete[]`. (That would be `std::vector`.) As for your argument: On the one hand, I can see the advantage of having the RTE taking care of `delete` vs. `delete[]`. OTOH, C++ offers a lot of alternatives (as `std::vector`) to avoid dynamic allocation altogether. But, as I've said in http://stackoverflow.com/questions/1245430/over-the-last-7-8-years-what-are-the-biggest-influences-on-c-programming/1246870#1246870, that's mainly a question of how C++ is taught, not what it offers.
sbi
tr1::array is a replacement for new[]/delete[]. It could allocate array on the stack. std::vector always allocates memory on the heap.
Kirill V. Lyadvinsky
No, Jla3ep, `array` is not a replacement for `new[]`. If you're using `new[]`, then it's because you don't know the number of elements until run time. But `array` has a non-type template parameter indicating its size, and such a parameter must be a compile-time constant. If you know the required number of elements at compile time, then you never should have been dynamic allocation in the first place. `array` is a replacement for fixed-size arrays, not those allocated with `new[]`. For that, use `vector`.
Rob Kennedy
Oh, yes... from that side :) Fixed my answer.
Kirill V. Lyadvinsky
+1  A: 

By using the STL container classes and the various boost:smart_ptrs, there's little need to ever explicitly call new or delete in your C++ code.

The few places you might need to call new (e.g, to initialize a smart pointer) use the Named Constructor Idiom to return your class type pointer wrapped in, e.g., a boost:shared_ptr.

But C++ and the STL work very very hard to allow you to treat most objects as value objects, so you can construct objects rather than pointers and just use them.

Given all this, there's little need to replace the new operator -- and doing so would introduce a host of problems, whether by requiring a garbage collector, or by reducing the fine low-level control C++ offers programmers.

tpdi
+4  A: 

Problem match new, delete, new[], delete[]

Not really a big deal.
You should be wrapping memory allocation inside a class so this does not really affect normal users. A single obejct can be wrapped with a smart pointer. While an array can be represented by std::Vector<>

cannot use #define with new as you could with malloc.

The reason to mess with malloc like this was to introduce your own memory management layer between your app and the standard memory management layer. This is because in C you were not allowed to write your own version of malloc. In C++ it is quite legal to write your own version of the new which makes this trick unnecessary.

Martin York
A: 

If your new language is garbage collected, you can avoid the new keyword. Thats what Python did (and Lisp did almost 5 decades ago!). Also see an answer provided by Peter Norvig for a similar question here. (Is no "news" good news?)

Vijay Mathew
In common lisp, the equivalent of 'new' is 'make-instance', so it's a function rather than a keyword/special form, but otherwise similar ( taking the class of the object and its initialisation parameters ).
Pete Kirkham
make-instance is part of CLOS, which in turn is implemented in Lisp itself. (SICP and "The Art of the Metaobject Protocol" will throw more light on this). Note that make-instance does not make any VM level calls to create a new instance of an object. In fact, the Lisp VM is not aware of the existence of classes and objects. So this cannot be compared with the implementation strategies followed by Java, etc.
Vijay Mathew
SCIP illustrates an object system in scheme as a teaching aid. Professiotnal Common Lisp implementations such as Allegro implement a 'native' CLOS rather than using conses for large performance gains.
Pete Kirkham
Agreed. BTW, how is CLOS implemented? It is implemented in Lisp itself. In the end, objects turn out to be procedures with state. This is simply illustrated in SICP and detailed in "The Art of the Metaobject Protocol". The point is, make-instance should not be confused with new.
Vijay Mathew
A: 

Sometimes you want to replace the constructor with a factory. This is a well known refactoring. Replace Constructor With Factory Method. So perhaps this is what the article meant?

Incidentally you will often see straight calls to new being replaced with a Factory Method.

DI frameworks such as Unity take this concept to another level. As you can see in the following C# code, there is no "new" applied to create the IMyClass interface:

IUnityContainer myContainer = new UnityContainer();
myContainer.RegisterType<IMyClass, SomeClass>();
IMyClass thing = myContainer.Resolve<IMyClass>();
RichardOD
On one of my experimental C++ projects, I actually wrote my class declarations in XML and generated headers for them, where the constructors were protected but exposed a static Create method that gave them well-behaved semantics and return smart pointers. :)
280Z28
A: 

The reason that C++ has a separate new operator ( or C malloc ) is primarily so that objects can be created whose lifetimes exceed the scope of the function which creates them.

If you had tail call elimination and continuations, you wouldn't care - the objects could all be created on the stack and have unlimited extent - an object can exist until you call the continuation that corresponds to the object going out of scope and being destructed. You might then need something to garbage collect or otherwise compress the stack so it doesn't become full of no-longer required objects ( Chicken Scheme and TinyOS 2 are two different examples for giving the effect of dynamic memory without dynamic memory at either runtime or compile time; Chicken Scheme doesn't allow for RAII and TinyOS doesn't allow for true dynamic allocation ), though for a large amount of code such a scheme wouldn't be vastly different to RAII with the facility to chose to change the order the objects are destructed.

Pete Kirkham