Skip navigation

Never Put A Sock In A Toaster

(~65:00 into the dvd)

A set of developer DUHs that I develop as I come across them in my own work

  • If you are setting some parameters that are specific to your dev machine, MARK machine specific settings with a comment //!!machine-specific. Before you try to run on another machine then, you can search for //!!mach and you will find all stuff you need to change.

    Origin is I set some parameters in an ogre app that were specific to my GPU. When I tried to run on another machine, it wouldn’t draw at all, and it said the error was coming “from ogre” (VERY generic exception). So it was a very hard bug to track down, and all the while you’re suspecting “maybe Ogre DOES have bugs”..

  • NEVER, please never, do a memset( this, 0, sizeof( SomeClass ) ) ; on any class or struct that inherits from some base class with virtual functions. You will zero out the virtual function pointers.
  • Don’t write member functions in your C++ code that mimic STL member functions. This leads to massive confusion. For example, some code I was reading recently had a class that wrapped TWO stl vectors (it needed them both internally). It them exposed 3 methods: .begin(), .end(), and .next() which were meant to help you iterate over the primary collection. The confusing part was USING this crap: was the class itself a std::vector? No. Then how/why does it have a .begin() and .end() method that looks EXACTLY as if the object WERE a vector? Second, it was confusing WHICH COLLECTION (as a user of the class) you would be iterating over. Remember the class had two collections.
  • Overloads should do the exact same thing to the inputs. For example, say you have two overloads: Matrix.Transform( Vector ) and Matrix.Transform( Point ). The only difference between Vector and Point practically is that Vector supports operator overloading.
    Now you would go and do something stupid like, I don’t know, make it so the translation elements of this matrix (third row) are ignored for Matrix.Transform( Vector ) and NOT for Matrix.Transform( Point ). No. That would be stupid and unintuitive, no? Well, that’s what Microsoft did.

    In the earlier System.Drawing namespace, (before Vector was created) they actually did this right, and provided 2 separate methods which both accepted Point only:

    TransformPoints Applies the geometric transform this Matrix represents to an array of points.
    TransformVectors Multiplies each vector in an array by the matrix. The translation elements of this matrix (third row) are ignored.
  • A math related one. If you happen to write a few matrix functions, PLEASE INDICATE WHETHER YOU ARE PREMULTIPLYING OR POSTMULTIPLYING vectors. Its important. If you created a matrix and intended it to be postmultiplied by a vector, but find out that the library wants to pre-multiply by the vector, you can just transpose your matrix before doing the multiply.
  • When working with angles that you increment (for example, you’re writing something with a spinny thing that goes round and round, with ever-increasing theta), make sure that the angle doesn’t get too big. In fact, its a good idea to regularly just

    // keep theta comfortably between -2*PI and 2*PI
    if( theta > 2*PI ) { theta -= 2*PI ; }
    else if( theta < -2*PI ) { theta += 2*PI ; }
    

    I once had a program that didn’t do this, and when theta got HUGE, the variable got corrupted and the graph looked REALLY weird. I saw the graph was fine, fine, fine, then sometime later it got twisted..

    so the problem was, theta was too big (duh!)

  • If you change the members of a structure or class, IMMEDIATELY UPDATE THE COPY CTOR OR PUT AN ASSERT IN THERE!
  • CHECK THE HRESULT OF EVERY COM OPERATION, NO MATTER WHAT, ALWAYS. A SMALL change can completely fuck up a perfectly good piece of code, and the COM failure can have happened literally HUNDREDS of lines of code before the symptom.
    ALWAYS check, and ALWAYS log the result, if not crash out and bail on fail.
  • Avoid stack allocated struct returns.

    For example, say you have a function

    STRUCT_TYPE initStruct()
    {
      STRUCT_TYPE t ;
      // .. do stuff
      return t ;
    }
    

    Then you call this by

    STRUCT_TYPE myStruct = initStruct() ;
    

    This is BAD because the return is by value. This means that the return t; statement:

    • Returns a COPY of t, and that is assigned to myStruct in the calling function
    • Then, DESTROYS t, invoking its destructor

    THIS IS BAD NEWS. Say STRUCT_TYPE has a destructor like:

    ~STRUCT_TYPE
    {
      delete[] array ;
    }
    

    Then what happens is, even though your returned a copy of STRUCT_TYPE, its internals might have been destroyed by the destructor call.

    So, write functions like this instead:

    void initStruct( STRUCT_TYPE & t )
    {
      // .. do stuff to t
    }
    

    Or:

    void initStruct( STRUCT_TYPE * t )
    {
      // .. do stuff to t
    }
    

    Or

    STRUCT_TYPE* initStruct()
    {
      STRUCT_TYPE * t = new STRUCT_TYPE ;
      // .. do stuff
      return t ;
    }
    
  • The first 2 examples don’t care how t is allocated, all they care is that t is allocated by the caller, so initStruct doesn’t decimate it when it returns and its scope ends.

    The last example heap allocates a STRUCT_TYPE object, so no destructor is invoked when t goes out of scope.

One Trackback/Pingback

  1. […] I see this as a HUGE defect in the library, ESPECIALLY since differing behavior is determined by OVERLOAD.. it just seems SO stupid and unintuitive. Another one for never put a sock in a toaster.. […]

Leave a comment