C/C++ Contributing Editors


Uncaught Exceptions: eWriter

Bobby Schmidt

Just when we’re fixin’ to swear at VC++ again, it turns around and does something right.


Copyright © 2001 Robert H. Schmidt

Writing for a print magazine can be quite humbling. Once I commit something to print, I lose my last chance for immediate retraction or clarification. Every word lives forever.

My only real opportunity for change — errata in later columns — is far from ideal. Errata logically are revision marks that physically live in a distinct piece; both the original and the errata are required to make the intended writing complete. Anyone reading just the original will get a partial picture. The full picture is distributed across space and time.

This is one large reason I’m preferring more and more to write for online publications: I can make corrections directly into the one canonical master copy, a form of iterative improvement and debugging on live published material. The moving finger writes and, having writ, can move back.

Bug-- of the Month

Q

Mr. Schmidt:

The attached file causes VC++ v6 to complain and threaten not to release memory.

If the destructor declaration in class X is made non-virtual, or if it is just removed, the files compile fine without complaint. I have not had an opportunity to try compiling this with any other compiler. I am not sure whether I have tripped over some C++ obscurity or a compiler bug.

I would appreciate your comments.

Regards — Bill Hay

A

Ron Burk recently started soliciting potential authors to write WDJ’s Bug++ of the Month column [1], which almost always picks apart some problem in Visual C++. Temporarily disregarding my day job, I spent about six seconds flirting with the notion of auditioning for that position. Sanity regained hold quickly, if only because I get an opportunity to write such a column here in CUJ.

I’ve reduced your sample to

#include <stddef.h>

class X
   {
public:
   virtual ~X();
   void *operator new(size_t);
   void operator delete
        (void *, size_t);
   };

X *x = new X; // warning here

When compiled, this code produces the warning

no matching operator delete found;
memory will not be freed if
initialization throws an exception

You’ve run into a pair of Visual C++ compiler bugs. I’ll describe what’s supposed to happen and then demonstrate how Visual C++ is actually behaving.

For the usual case of

x = new X

the program calls the non-placement allocation function

operator new(size_t)

to allocate space for *x. In the more general case of

x = new(a, b, c) X

the program calls the placement allocation function

operator new(size_t, A, B, C)

to allocate the space, where the arguments a, b, and c are passed in as parameters to operator new. In all cases, the corresponding expression

delete x

calls either

operator delete(void *)

or

operator delete(void *, size_t)

to free the allocated space, depending on which overload is available [2]. The void * parameter points to *x’s space. In the second overload, the size_t parameter gives the size of that space. (This matches the size_t parameter in the operator new call.)

Now consider what happens during

x = new(a, b, c) X

if the X constructor throws. The throw occurs after the call to

operator new(size_t, A, B, C)

has succeeded, meaning the space has already been allocated. Unless you wrap the new expression in a try block and manually free the memory in an exception handler, your program will suffer a memory leak.

To avoid this problem, you can arrange for the memory to be freed automatically. According to the language rules, the program will call the placement deallocation function

operator delete(void *, A, B, C)

to free the space if the X constructor in

new(a, b, c) X

throws [3]. In the usual case of

new X

there are no placement arguments; instead, the program automatically calls a non-placement deallocation function to free the memory.

Thus, for the general case of

operator new(size_t, A, B, C)

there are three potentially matching deallocation functions:

Note that the second operator delete overload is a special case of the third, where A, B, C is replaced by size_t. This second overload is called automatically when the X constructor throws in

size_t n;
x = new(n) X;

In this instance, operator delete serves as a placement deallocation function matched to the placement allocation function

operator new(size_t, size_t)
//           ^       ^ argument n
//           ^ size of space

Thus, this same operator delete overload serves two distinct roles:

(Interesting implication: operator delete(void *, size_t) does not inherently know how it was called, or how to interpret its extra parameter. This is the only deallocation function to suffer these twin ambiguities [4].)

With that background, it’s time to analyze Visual C++.

Build and run my test program (Listing 1) with Visual C++. You’ll find that three of the four test cases run correctly. The only failure comes in test 3: the program is supposed to automatically call operator delete but doesn’t. That failed test case represents compiler bug #1.

At compile time, you’ll also see two instances of the familiar compiler warning: one for test 1, and one for test 3. However, test 1 actually runs correctly. That first warning is a red herring and represents compiler bug #2.

(I’ll note for the sake of completeness that my other two test systems — Metrowerks CodeWarrior Pro v6 for Mac OS, and EDG v2.45 for MS-DOS — get all test cases right without spurious warnings.)

Advantage Microsoft

Q

C# and Java: What is the difference? When will there be a lawsuit from Sun, and what happened to J++? Thanks — Bill Eidson

A

Since I’m watching the Australian Open as I type, I’ll take my inspiration from John McEnroe: “You cannot be serious!”

You Can Run, but You Can’t Hide

Q

I have tried compiling the attached code on five different platforms (three Unix vendor-supplied compilers, gcc, and MSVC 6.0), and only MSVC 6.0 successfully compiles it. The rest complain something to the effect of “No matching function for call to Override::func(int,int),” etc.

One even complains about func being redeclared after the Base::func; access declaration. Not all the mentioned compilers support the using Base::func; declaration, so I went with the least common denominator syntax.

So, given that I can’t change the existing Base API, what is the correct format for this combination of overloading and overriding? Is there one? — Michael McCarty

A

Well, this is a nice turn about: the Charlie Brown of compilers goes from being the goat in the first question to being the hero here. My stock options are pleased.

Your code appears as Listing 2, with one slight modification: I’ve replaced <iostream.h> with <iostream>. That first header is not part of the Standard C++ library, even though many library vendors support it. I strongly recommend that you prefer <iostream> wherever you can.

Now to your real question.

Your code should build and run, giving the output

Override::func()
Base::func(int)
Base::func(int, int)

The key is the access declaration

class Override : public Base
   {
public:
   Base::func; // access declaration
   void func();
   };

which trumps the usual base-member hiding and overriding rules. From Subclause 11.3:

The access of a member of a base class can be changed by mentioning its qualified-id in the derived class declaration. Such mention is called an access declaration. The effect of an access declaration qualified-id; is defined to be equivalent to the declaration using qualified-id;.

The Standard defines an access declaration as equivalent to a using declaration, which (from 7.3.3):

...introduces a name into the declarative region in which the using-declaration appears. That name is a synonym for the name of some entity declared elsewhere.

The member name specified in a using-declaration is declared in the declarative region in which the using-declaration appears.

When a using-declaration brings names from a base class into a derived class scope, member functions in the derived class override and/or hide member functions with the same name and parameter types in a base class (rather than conflicting).

For the purposes of overload resolution, the functions which are introduced by a using-declaration into a derived class will be treated as though they were members of the derived class.

The name in an access declaration acts as if it were physically declared in the scope of the access declaration. In your particular example, the access declaration

Base::func;

appears in the scope of Override and thus effectively declares all of the Base::func overloads within Override.

At first glance, this would seem to give Override four func overloads:

But as the Standard provides, the two func() declarations don’t conflict; instead, Override::func() overrides Base::func(). Class Override therefore effectively declares three func overloads: one directly, and two via the access declaration.

I suspect your errant compilers are either ignoring the access declaration or are misapplying the Standard’s rules regarding access declarations.

As the Standard indicates, and as you mention, you should be able to rewrite Override as

class Override : public Base
   {
public:
   using Base::func; // using declaration
   void func();
   };

This rewrite employs using-declaration syntax instead of access-declaration syntax. As the Standard indicates in Footnote 100:

Access declarations are deprecated; member using-declarations (7.3.3) provide a better means of doing the same things. In earlier versions of the C++ language, access declarations were more limited; they were generalized and made equivalent to using-declarations in the interest of simplicity. Programmers are encouraged to use using-declarations, rather than the new capabilities of access declarations, in new code.

Had you declared Override with neither an access declaration nor a using declaration:

class Override : public Base
   {
public:
   void func();
   };

the function Override::func would have hidden the Base functions func(int) and func(int, int), and the calls

o.func(1);
o.func(1, 1);

would not have compiled. Instead, you would have needed to qualify the func calls as

o.Base::func(1);
o.Base::func(1, 1);

Notes

[1] WDJ = Windows Developer’s Journal, a sister publication of CUJ. Strange but true: Ron Burk and our own Marc Briand were college roommates back in the Carter administration. [Kinda scary, isn’t it? — mb]

[2] If both are available, operator delete(void *) wins. See Subclause 3.7.3.2.

[3] Provided the appropriate operator delete overload exists and is unambiguously available. Otherwise, no automatic deallocation occurs, and the memory is leaked. Subclause 5.3.4 has all the details.

[4] Under normal operating conditions, that is. Nothing prevents you from manually calling operator delete as you would any other function and passing any arguments you want.

Although Bobby Schmidt makes most of his living as a writer and content strategist for the Microsoft Developer Network (MSDN), he runs only Apple Macintoshes at home. In previous career incarnations, Bobby has been a pool hall operator, radio DJ, private investigator, and astronomer. You may summon him on the Internet via BobbySchmidt@mac.com.