Exploring Solution Spaces © Copyright 2003-2006, by C. Keith Ray
   


About
Exploring Solution Spaces, Keith Ray's blog on Software development and other topics.

Send comments to:
keithray@mac.com

For Agile Training, eLearning, or Coaching contact:
Industrial Logic, Inc.
866-540-8336 (toll free)
510-540-8336 (Berkeley, California)

Links
xpminifaq
Résumé
“Adopting XP” Article 2002 (pdf)
“ Refactoring” Article 2006
AYE Conference
Lucien W. Dupont
Elisabeth Hendrickson
Johanna Rothman's Managing Product Development
Brian Marick's Exploration Through Example
Esther Derby's Insights You Can Use
Laurent Bossavit's Incipient(thoughts)
Dale Emery's Conversations with Dale
Martin Fowler's Bliki
Creating Passionate Users

Archives

  • 2003
  • 2004
  • 2005
  • 2006
  • 2007
  • 2008
  • Subscribe
    RSS Exploring Solution Spaces XML


           
    2007.Jan.31 Wed

    Test-Driving C++

    In most languages, when I'm using Test Driven Development to create a class, I only put into that class those methods or fields that I needed to pass a test. C++ has some exceptions to that, given how the compiler will generate aspects of a "canonical c++ class" for you.

    I should explain the idea of a "Canonical C++" class. Imagine that I have this code:

    class Buddy
    {
    public:
       Icon* myIcon;
       std::string myName;
    };
    

    Now, I didn't write a constructor, destructor, nor an assignment-operator, but the compiler did create those for me. It's as if I really wrote the following code:

    class Buddy
    {
    public:
        Icon* myIcon;
        std::string myName;
    
        Buddy() // default constructor
            : myName() // invokes std::string's default constructor
        { // myIcon is not initialized, it probably has a garbage value here.
        }
    
        Buddy(const Buddy& other) // copy constructor
            : myIcon( other.myIcon ) // copy the variable's value
            , myName( other.myName ) // invokes std::string's copy constructor
        { 
        }
    
        ~Buddy() // destructor
        {
        } // invokes std::string's destructor for myName.
    
        Buddy& operator=(const Buddy& other)
        { // assignment operator
            myIcon = other.myIcon; // copy the variable's value
            myName = other.myName; // call std::string's assignment operator
        }
    };
    

    A "canonical" C++ class has default constructor (and/or other constructors), copy constructor, destructor, and assignment-operator. These may be defined by the programmer or created by the compiler.

    And this invisible compiler-generated code can be wrong, particularly if ownership of pointers or other resources is involved. Let's say that I test-drive a default constructor that sets up myIcon to point to a newly-created Icon object, and write the corresponding destructor code to delete the Icon object. It's hard to verify the "state" of an object after its destructor is called ('cuz it's GONE), but there are a few tricks to verify the behavior of a destructor that I won't get into here.

    class Buddy
    {
    public:
        Icon* myIcon;
        std::string myName;
    
        Buddy()
            : myIcon( NULL )
            , myName( "no name" )
        {
            myIcon = new Icon(Icon::DEFAULT_ICON);
        }
    
        ~Buddy()
        {
            delete myIcon;
        }
    };
    
    SPECIFY_(Context,BuddyHasDefaultNameAndIcon)
    {
        Buddy* aBud = new Buddy;
        VALUE( aBud->myName ).SHOULD_EQUAL( "no name" );
        VALUE( aBud->myIcon ).SHOULD_NOT_EQUAL( NULL );
        delete aBud;
    }
    

    This test will pass. (by the way, I'm using "ckr_spec" here, a Behavior-Driven-Design framework I've written in C++ in my spare time. I'll publish more about ckr_spec one of these days.) However, this test doesn't exercise the compiler-created copy-constructor and assignment operators. AND THOSE ARE WRONG. Nothing (besides self-discipline) prevents anyone from writing the following (crashing) code:

    void crashingCode()
    {
        Buddy keith;
        Buddy keithClone(keith); // calls compiler-created copy constructor
        Buddy anotherKeith;
        anotherKeith = keith; // calls compiler-created assignment operator
    
    // destructors are called invisibly here - crash deleting the same Icon
    // object 3 times. (Also leaks an Icon object, too.)
    }
    

    The compiler-created copy constructors just copy the pointer to the Icon object. They don't create a NEW Icon object. So in "crashingCode" above, the Icon object created in the constructor of "keith" gets deleted three times, when the destructors for the "keith", "keithClone", and "anotherKeith" objects get called at the end of the function.

    Therefore, when I'm test-driving a C++ class, very early on, I make a decision. Is this a "value" class that is going support the copy-constructor and assignment-operator, or an "entity" class that should never be copied because the instance represents something with a persistent identity? (These are some over-simplified ideas from Domain-Driven Design.) I can change my mind later, of course.

    If my class is going to allow "value" semantics, then I'll need to write some tests to assure that the copy-constructor and assignment operator function correctly, whether I've written them, or the compiler has generated them.

    If I'm not going to allow "value" semantics, then I need to signal to the compiler and to my fellow programmer not to generate or use the copy-constructor and assignment-operator. Declaring them private and unimplemented is how to do that.

    class Buddy
    {
    public:
        Icon* myIcon;
        std::string myName;
    
        Buddy()
            : myIcon( NULL )
            , myName( "no name" )
        {
            myIcon = new Icon(Icon::DEFAULT_ICON);
        }
    
        ~Buddy()
        {
            delete myIcon;
        }
    
    private:
        Buddy(const Buddy& other); 
            // don't implement copy constructor
    
        Buddy& operator=(const Buddy& other);
            // don't implement assignment operator
    };
    // the crashingCode example will not compile now.
    

    For entity objects, quite often I don't want to allow the default constructor either, so I would declare that private and unimplemented as well.

    The moral of the story is that in C++, sometimes you have to write code to prevent the compiler from writing the code for you. Just add that to your TDD/BDD development process.

    [/docs] permanent link