Tuesday, June 26, 2012

Warning Level 4

I became a better programmer recently.  Very recently in fact.  It happened the second I finished C++ Coding Standards by Herb Sutter and Andrei Alexandrescu (Not Alexander Andreiscu as I keep forgetting!)  Two brilliant people who I will one day match!  I hope.

I've always loved picturing learning programming as a constant wave between thinking you know nothing to thinking you know everything and back again.  This book gave me about a dozen full 'waves' as it told me what I didn't know and then taught me it.  Besides that it also pushed good Coding Standards which I'll strive to meet.  The obvious one from the title, is warning level 4, warnings as errors.  It touches on a lot of things you know you should do and then presents the (very compelling) reasons why you should do it.

A few things that really stood out to me:

Item 30.  Avoid overloading &&, || and , (comma)

I probably should have known that the order of operations would be lost.  But it's really easy to assume they'll be kept.  This was a really good fact to pick up, especially considering I've been programming in Scala a fair bit (I love that language) in which you can take advantage of order of operation in operator overloading.

Item 44.  Prefer writing nonmember nonfriend functions.

This isn't something that came as a shock but, the arguments for it really stood out to me.  I'm still a student and as a student I've been told that objects are awesome.  Use objects.  Everything should be in a class (okay, maybe not that far but definitely far enough to assume that last one.)  I'll admit that one of that bad things I've always done is keeping operator overloads that should be nonmember functions in the class.  I always thought it made more sense.  I knew I shouldn't do it but, I always figured I'd wait until the next project to start doing it correctly.  Well, the next project starts now, and those operators (along with a bunch of other functions) are being taken out of the class.

Item 49.  Avoid calling virtual functions in constructors and destructors.

Because they aren't virtual there... Whaaat?  Now, I don't think this is something I've ever done (or thought of doing for that matter.)  But if I did, I definitely wouldn't have expected that.  And pure virtual functions cause undefined behaviour.  Feels a bit minefield-y.  One of the solutions is a factory function, definitely going to be my approach in the future!

Items 92- 95. Cast wisely

These were the ones that really changed the way I look at what I was taught.  For instance, I was taught to use C-style casts on pointers.  This is bad.  Don't do this.  There are three (four including const) casts that are possible.

dynamic_cast<NewType>(object) is the safest and will never be used by C-style casts.  It performs safety checks at compile time and run time.  It does incur an overhead but, one should not optimize prematurely.  Use it as much as you can.  Worst come to worst, it's easy to write a function that will use dynamic cast only in debug.  If the cast fails it returns a null pointer.

static_cast<NewType>(object) is the second safest and is at least portable.  It has no runtime overhead.  Aside from that it is very like dynamic_cast.

reinterpret_cast<NewType>(object) is not for everyday programming (depending on what you do everyday, I suppose.)  In short, it's functionality is implementation specific.  It's unsafe, unreliable and can result in crashes and undefined behaviour.

As stated on http://www.cplusplus.com/doc/tutorial/typecasting/ :
"The conversions that can be performed by reinterpret_cast but not by static_cast have no specific uses in C++ are low-level operations, whose interpretation results in code which is generally system-specific, and thus non-portable."


 So basically, you don't need it unless you know exactly what it's doing.

Now how does this relate to C-style casts?

Well

#include "Derived.h"
void fun(Base* base){
  Derived * myDerived = (Derived*) base;
}

and

#include "Base.h"
class Derived;
void fun(Base * base){
  Derived * myDerived = (Derived*) base;
}

Are completely different.  The first will use a static_cast and everything will be okay (if you pass in a Derived* to base).  The second will use reinterpret_cast and, as we now know, reinterpret_cast should not be used here.  Why is it used?  Because the compiler has no idea that Derived is Derived from Base, it just knows that it's a class.  This becomes a huge problem when multiple inheritance is used as it could result in a pointer of type Base2 pointing at the Base1 implementation in Derived.

There are tons of other create points in the book and I recommend it to everybody.  If anybody notices anything wrong in this post please point it out.

Quick note, I understand that reinterpret_cast is useful for somethings, the book realizes this as well and provides Exceptions for almost all of the Items.

No comments:

Post a Comment