Thread overview
Destructor called without constructor bug revisited
May 12, 2003
KarL
May 12, 2003
Richard Grant
May 12, 2003
Richard Grant
May 12, 2003
I finally bought the WDJ CD-Rom and is able to locate the article on the old old bug:

This is the one which VC, BC and GCC no longer has the bug but DMC++ still has the bug.

WDDJ November 1995 (For bug fixing reference only, no copyright violation indented)

--------------------------------------------------------------------------------

Last month's bug was something of an anomaly for this column. Instead of exploring a problem unique to Microsoft or Borland, I took a look at a C++ language feature that gave both vendors trouble. The placement syntax used with operator new had one set of implementation problems with Microsoft, and a slightly different set with Borland.

This month, I take a look at another problem that seems to have slipped past the test suites of both companies. Oddly enough, two different compilers generate equivalent buggy code!

C++ Class Member Functions
Member functions of C++ classes look and act much like the normal C functions I cut my teeth on long ago. In fact, C programmers can feel pretty comfortable with member functions if they view them as old-fashioned functions with a pair of superpowers:

  a.. Member functions get a secret argument not found in the parameter list. Referred to as this, it is a pointer to the object that was used to call the member function.
  b.. Member functions have access to protected and public data members of objects in their class (including this). This includes member functions in addition to data. Member functions can also use a shorthand syntax to access these data members, omitting explicit references to this. (e.g., using length instead of this->length).
Static Member Functions
This mental metaphor works pretty well for most member functions, but as usual, C++ has one more trick up its sleeve. In this case, the surprise is the static member function.

Static member functions give up one of the special attributes of member functions: they don't have the implicit this parameter. This means that you can call a static member function either with or without an object of the appropriate class. For example, if bar is a static member function of class foo, you can call it several different ways:

void test( foo *foo1 )
{
    foo foo2;
    foo1->bar();   //Called using an object pointer
    foo2.bar();    //Called using an object reference
    foo::bar();    //Called without an object

Static member functions are generally used to manage the environment of an entire class of objects. For example, in Hewlett Packard's release of the Standard Template Library, static member functions are used to manage memory pools for several container classes. Static member functions allocate big blocks of memory, then parcel out smaller pieces to individual members as necessary.

So what good is this capability? Static member functions have to give up the implicit use of an object. It would seem that static member functions could easily be replaced by global functions classified as friends of the given class.

While this is true, there are good reasons for using static member functions instead of global friend functions. Most important, static member functions help programmers adhere to the concept of encapsulation. If all the functions that modify objects of a given class are confined to the class definition, it becomes much easier to keep track of who does what. Encapsulation is one of the pillars that support object-oriented programming, and OOP, we are often told, is a good thing.

A Compiler Test for Static Member Functions
bug1195.cpp (Listing 1) shows a relatively short program that tests the ability of a compiler to properly use static member functions. The three subroutines, Test_1(), Test_2(), and Test_3() use three different techniques to call static member functions. Test_1() uses the most conventional technique, calling the function explicitly.

Test_2() and Test_3() call the static member function bar() by way of an object of class foo. The object in Test_2() is a local automatic variable; that in Test_3() is a temporary variable.

Class foo in bug1195.cpp contains some debug code that should be familiar to regular readers of this column. By printing out short messages when the object is created and destroyed, you can ensure that the compiler has generated the proper calls to constructors and destructors.

Sharp-eyed reader Aaron Margosis noticed that the output from this test program had a problem:

Test 1: bar
Test 2: ctor bar dtor
Test 3: bar dtor

The output from Test_3() indicates that the temporary foo object was destroyed without ever being properly constructed! This is clearly a major problem. Any moderately complex class is bound to corrupt data structures left and right when destroyed in this fashion.

Responses from Borland and Microsoft
The really odd part about this bug is that it is common to Borland C++ 4.5 and Visual C++ 1.5. Seeing a bug such as this in a mature product is surprising enough, but I would never have expected to see it pop up in two completely different places at once.

Brian Myers from Borland acknowledged the bug and said it will be fixed in a future release. Presumably, this will be Borland C++ 5.0.

John Browne from Microsoft had this to say:

  This is fixed in our current product, Visual C++ 2.2.

Those of you using the 16-bit version of Visual C++ will presumably wonder if you can expect a fix as well.

Bug++ Rewards
Reader Margosis will be wearing his new WDJ t-shirt soon after this column sees print. You can join Aaron and several international supermodels on Mr. Blackwell's Best-Dressed list by sending us your favorite C++ bugs. Just write them up and post them to us at wdletter@rdpub.com. With new releases of Borland's and Microsoft's compilers just around the corner, we're hoping for a full mailbox!

Mark Nelson is a programmer for Greenleaf Software in Dallas, Texas. Mark is the author of The C++ Programmer's Guide to the Standard Template Library, from IDG Books, as well as The Data Compression Book, from M&T Books. You can reach Mark on CompuServe at 73650,312.

Listing 1 bug1195.cpp - Problematic static member function call
//
//  This short program demonstrates a problem
//  found when calling a static member function.
//  For some reason, both VC 1.x and Borland 4.5
//  call the destructor for a temporary object that
//  was never constructed properly.  This error only
//  shows up in Test_3(), where a temporary object
//  is used to call a static member function.
//

#include <iostream.h>

class foo {
    public:
        foo() { cout << "ctor "; }
        foo( const &foo ) { cout << "copy "; }
        ~foo() { cout << "dtor "; }
        static void bar() { cout << "bar "; }
};

//
// Test 1 performs properly.  It doesn't construct
// or destroy any foo() objects.
//
void Test_1() {
    foo::bar();
}

//
// Test 2 works properly as well.  It constructs a
// single object, then destroys it when the function exits.
//
void Test_2() {
    foo f;
    f.bar();
}

//
// Test 3 has a problem.  It doesn't create the temporary
// object used here, but it destroys it!
//

void Test_3() {
    foo().bar();
}

void main()
{
    for ( int i = 1 ; i <= 3 ; i++ ) {
        cout << "Test " << i << ": ";
        switch ( i ) {
            case 1 : Test_1(); break;
            case 2 : Test_2(); break;
            case 3 : Test_3(); break;
        }
        cout << endl;
    }
}




May 12, 2003
Tested this with beta..

With exception handling enabled the following fails to compile.. without exception handling, compiles ok.

struct A {
static void bar() { }
~A() { }
};

void fn() {
A().bar();
}

int main() {
fn();
}
// Internal error: eh 688

Richard


May 12, 2003
Tested with latest beta..

This performs as the OP to "c++" indicates, and executes a user defined d'tor without executing the user defined c'tor. Just removed text and stuff to make it easier to see.

#include <iostream>

using std::cout;
using std::endl;

class foo {
public:
foo() { cout << "ctor "; }
foo( const &foo ) { cout << "copy "; }
~foo() { cout << "dtor "; }
static void bar() { cout << "bar "; }
};

//
// Test 1 performs properly.  It doesn't construct
// or destroy any foo() objects.
//
void Test_1() {
foo::bar();
}

//
// Test 2 works properly as well.  It constructs a
// single object, then destroys it when the function exits.
//
void Test_2() {
foo f;
f.bar();
}

//
// Test 3 has a problem.  It doesn't create the temporary
// object used here, but it destroys it!
//

void Test_3() {
foo().bar();
}

int main()
{
for ( int i = 1 ; i <= 3 ; i++ ) {
cout << "Test " << i << ": ";
switch ( i ) {
case 1 : Test_1(); break;
case 2 : Test_2(); break;
case 3 : Test_3(); break;
}
cout << endl;
}
}