tags:

views:

423

answers:

3

While reading proggit today, I came upon this comment in a post about how the top places in the Google Ai challenge were taken by C++. User reventlov declares

The biggest problem I have with C++ is that it's waaay too easy to think that you're a "C++ programmer" without really understanding all the things you need to understand to use C++ acceptably well.

You've got to know RAII, and know to use namespaces, and understand proper exception handling (for example, you should be able to explain why the pop() methods in the STL do not return the values they remove). You've got to know which of the three generations of functions in the standard library is the right one. You should be familiar with concepts like PIMPL. You need to understand how the design of the standard library (especially the STL) works. You need to understand how macros interact with namespaces, and why you usually shouldn't use macros in C++, and what you should use instead (usually templates or inlines, rarely a class). You need to know about boost.

I think I'm one of those clueless C++ programmers he mentions. To keep this brief, my questions are

  1. Can you give an example of a typical RAII oversight mistake, e.g. where best practices dictate the use of RAII but programmers have implemented using some other way?
  2. Why doesn't the pop() methods in STL return the value they remove?
  3. I read the Wikipedia entry for PIMPL, didn't understand any of it. Can you give an example of a typical usage of the PIMPL idiom.
+4  A: 

Read Herb Sutter's "Exceptional C++" and "Exceptional C++ Style".

n0rd
I was just about to say that. "Exceptional C++" covers these three topics (and others) very thoroughly.
Mike Seymour
+8  A: 

I'll answer point 2, and leave the rest for others. The main reason why pop doesn't return the removed value is due to exception safety.

First, understand that C++ containers (unlike, say, Java's ones) hold their objects by value. That means that if you want the container to return the object at popping time, it has to return you that object by value, by copying the object being removed. In contrast, by having you access top before pop, it can simply return a reference to the top element, and you can copy it to your heart's content before popping it. (Whereas, if pop returned the element by reference, it'd be a dangling reference, as the object is no longer in the container.)

The consequence of making pop return by value (aside from the inefficiency involved in copying the object being removed) is that it jeopardises exception safety. Ideally, if an operation throws an exception, the state of the objects involved is unchanged. But if pop were to return the removed object by value, what if the copy constructor for that object failed? The object would already have been removed from the container, and thus the state is already changed.

This is wordier than I wanted to make it, but hopefully gives you an idea why pop returning a value is a bad idea.

Chris Jester-Young
This doesn't explain why other mutator functions in the standard library do return non-builtin types by value (admittedly usually iterators, whose constructors are *unlikely* to be capable of throwing). But yes, the fact that they don't offer a strong exception guarantee doesn't mean that top/pop shouldn't.
Steve Jessop
`template<class C> typename C::value_type pop(C c.pop(); return copy; }` The only exception safety issue is if the item's dtor can throw, but throwing dtors should be *extremely* rare, and a non-throwing dtor could easily be made a requirement to call this pop(), similar to other container requirements.
Roger Pate
@Roger: That's fine, but (on exception safety) compilers are not required to implement RVO, in which case, the `return copy` would cause the copy constructor to be called (again). (P.S. Throwing dtors are Very Bad News (tm), because if it happens during stack unwinding, `std::terminate` gets invoked, or something to that effect. So one can/should safely assume that this shouldn't happen.)
Chris Jester-Young
@Chris: Yes, it could throw when returning, but that's not a violation of exception safety: my pop function still makes the basic guarantee (http://www.boost.org/community/exception_safety.html, http://en.wikipedia.org/wiki/Exception_handling#Exception_safety) as the container is always in a consistent state and any dtors are executed.
Roger Pate
+9  A: 
  1. A good example where RAII is crucial but sometimes forgotten is when locking a mutex. If you have a section of code that locks a mutex, performs operations, then unlocks it, if the operations throw an exception or otherwise cause the thread to die, the mutex remains locked. This is why there are several scoped lock classes (like QMutexLocker) since as stated here, you are guaranteed that the destructor will run. So if you use a scoped lock it will always unlock on destruction preventing a dead lock.

  2. Pop returns void for the sake of speed: SGI FAQ, and to prevent exceptions that may be thrown by the objects copy constructor.

  3. PIMPL is used heavily by the Qt framework to provide binary compatibility. It allows you to hide all the internals of a data structure from the public API. This means, if you want to add private members to a class, you add it to the d-pointer. This maintains Binary Code Compatibility since the only data member exposed is a pointer.

Adam W
+1 for point 2: SGI's decision in designing the STL was not explained in terms of exception-safety, but efficiency. Of course that doesn't show whether the C++ standardisation process retained the same rationale, or came to the same conclusion another way.
Steve Jessop