C/C++ Contributing Editors


Uncaught Exceptions: Semper Fi

Bobby Schmidt

Bobby reassures us that the dark side of the force need not be all that dark.


Copyright © 2000 Robert H. Schmidt

I have formally rejoined Microsoft's employ. Long-time Diligent Readers will note this is my third trip to the altar with Bill G & Co. The karmic implications of such on-again off-again romance are not lost on me.

Microsoft courted me pretty hard this time. Part of my attractiveness to them, ironically enough, is this very magazine. The people with whom I'll be working seriously value the independent perspective I bring. Thus, I have neither need nor desire to abandon CUJ and my other freelance activities [1].

I did not reach my decision lightly. In fact, Microsoft first approached me over six months ago. In the end, I was strongly influenced by three factors:

Finally, I expect my new relationship to benefit you, for I now have Microsoft-sanctioned opportunity and mandate to deeply affect things I believe you care about. I can't discuss most of those things — not yet, anyway — so you'll have to take my word in the short term. Stay tuned.

Sixteen Tons

Q

When I compile the following code with Visual C++ 6:

template<class T>
class A
    {
public:
    class iter
        {
    public:
        void fn();
        };
    iter first()
        {
        return iter();
        }
    };

template<class T>
void A<T>::iter::fn()
    {
    }

int main()
    {
    A<int> a;
    a.first().fn();
    return 0;
    }

I get this linker error:

unresolved external symbol
"public: void __thiscall
   A<int>::iter::fn(void)"
(?fn@iter@?$A@H@@QAEXXZ)

Am I doing something wrong or is this a compiler bug? I realize I could put the function definition inside the class but for larger functions I prefer not to. Both CodeWarrior 4 and gcc 2.95.2 compile and link without any problems.

Thanks! — Andrew McKinlay

A

Oh great — a chance to prove I haven't sold my soul to the company store.

I'm certain that you've hit a Visual C++ bug. At first I thought the bug was in Microsoft's name decoration/mangling scheme. But after some research, I now believe the bug is in Microsoft's code generator.

From what I can tell, the bug affects functions satisfying all of the following conditions:

Under these conditions, the actual definition of the instantiated function doesn't appear in the generated object file. You can see evidence of this in the .asm and .cod files generated from the original .cpp file: where you expect a PUBLIC definition of the function instantiation, you instead find an EXTRN reference with no definition.

You must change one of the above conditions. Possibilities:

I've tested all of these changes; any one of them allows the code to link. At the same time, my analysis has not been exhaustive. If you want to know the full score, I recommend you query Microsoft's Knowledge Base [2], since I have to believe that Microsoft is already aware of this bug.

Declarator's Revenge

Q

I wrote a program whose aim is to make template functions as members of a class (rather than global).

The program does not compile. May I know about the problem with this?

With thanks in advance. Sincerely — Gupta, R.K.

A

I've simplified your sample to:

class X
    {
public:
    X();
private:
    template<class T>
        void f(T);
    };

X::X()
    {
    f(1);
    }

X::template<class T> // error here
void f(T)
    {
    }

X x;

To better understand the problem, consider how you would define f if it were not a template function. Following your example, the definition would be:

X::
void f(T)
    {
    }

which is lexically equivalent to:

X::void f(T)
    {
    }

Diligent Readers will recognize this as Bad Form. The correct syntax is:

void X::f(T)
    {
    }

Adapting this form to your example:

template<class T>
void X::f(T)
    {
    }

I don't blame you for being confused. Template syntax does not play well with the rest of C++. To help avoid such problems in the future, keep in mind this simplified grammar for function template declarations [3]:

template<...>
&decl-spec declarator(...)

In the corrected example,

void

is the decl-spec (or declaration specifier), while

X::f(T)

is the declarator. Since

X::

is part of the declarator, it can't possibly appear before

template<class T>

Herb Roasted

Q

Hi!

I very much enjoy reading CUJ and often find useful information on various topics. One such instance was in Herb Sutter's article "Using auto_ptr Effectively" from Vol. 17, Issue 10 [October 1999]. The whole article was informative but to me the most exciting concept was what he called "Pimpl," separating the implementation of the class from the header file. I was not familiar with this concept despite having felt the need for it on various occasions.

Just now I found myself writing a class that could benefit from this concept. I tried using auto_ptr as the article suggested. I was writing from memory and it seemed strange. From what I remembered, all that is supposed to appear in the header was:

class C
    {
public:
    ...
private:
    class CImpl; // forward declaration
    auto_ptr<CImpl> pimpl_;
    };

When considering this it seemed to me that the template auto_ptr couldn't be correctly instantiated on only a forward declaration. I compiled the code anyway just for the hell of it. Sure enough, when compiled under VC6 warning level 3 I got the following warning:

deletion of pointer to incomplete
   type 'CImpl';
no destructor called

Knowing that I was born senile I assumed that I had missed some fine point and dug through my pile of CUJ and found the article. But the code I used was the same as the code published. I may be missing something but it seems that auto_ptr can't really help with Pimpl as the whole point is not to declare the implementation of the implementation class before declaring the pointer to it. — Joey Edelstein

A

You are referring to Example 4(b) from Herb's article. The relevant parts of that example appear as Figure 1.

When I try to build those parts, all of my C++ translators cough up the warning you saw. The translators are flagging possible (but not inevitable) undefined behavior. From the C++ Standard [4]:

If the object being deleted has incomplete class type at the point of deletion and the complete class has a non-trivial destructor or a deallocation function, the behavior is undefined.

The undefined behavior's context is somewhat complicated:

Conclusion: to avoid undefined behavior, the owned CImpl subobject must be deleted in C.cpp.

Corollary: to avoid undefined behavior, the containing C object must be destroyed in C.cpp.

Reality: C objects are destroyed anywhere but C.cpp.

C does not declare an explicit destructor. To compensate, the translators provide an implicit destructor that acts as if it were declared:

class C
    {
    // ...
public:
    inline C::~C();
    };

That implicit inline destructor is most likely synthesized within each translation unit that destroys a C object. In Figure 1, the only translation unit destroying such an object is main.cpp (after preprocessing). Since CImpl is not a complete type in that translation unit, the implicit destruction of the C object — which begets deletion of the CImpl subobject — yields undefined behavior.

The solution: define an explicit empty C destructor within C.cpp, as I've shown in Figure 2. With this change, the implicit C destructors are no longer generated; hence, all CImpl deletion occurs in the context of the explicit C destructor. As Cimpl is a complete type in this context, the Cimpl deletion no longer yields undefined behavior. The order of the universe is thus restored.

Herb's article is an excerpt from his book Exceptional C++. While composing your answer, I emailed Herb about the undefined behavior. From his response, I can affirm the following:

Context-Free const

Q

Bobby,

I wonder if you might give me a helping hand on a problem with const pointers. I have a base class B whose instances shall be created by a factory class:

struct B
    {
    int id;
    };

struct factory
    {
public:
    static B * const create()
        {
        return new B;
        }
    };

The method factory::create shall return a pointer that allows access to B::id but forbids reassignment:

int main()
    {
    B *b1 = factory::create();
    B *b2 = factory::create();
    b1->id = 1; // okay
    b2 = b1;    // okay, too!
    }

The sample works as desired if we define b2 as:

B *const b2 = factory::create();
b1->id = 1; // okay
b2 = b1;    // error

Why don't the pointers b1 and b2 adapt the characteristics as defined in factory? It would be great if you can help me. (I want to make the pointers' behaviour transparent to the class user.)

Regards — Volker Lermann

A

I've made some changes to your original sample:

Now on with the show.

If I'm reading your email right, you are misinterpreting const's role. const is not a "characteristic" that objects "adapt" from one another. const is instead a property established exclusively by an object's declaration, independent of the const properties of any other objects — including those used as initializers.

The const in question comes from the declaration:

B * const create()

const here means exactly one thing: that create's return value is of type "const pointer to B." That's it [6]. The const-ness of this return type does not somehow attach itself to b1 and b2 as they initialize from the return value. No, b1 and b2 instead acquire their const-ness from their declarations:

B *b1 = ...;
B *b2 = ...;

These declarations tell us that b1 and b2 have type "pointer to B." Again, their const-ness is not dictated or influenced by what appears to the right of =.

As you found, changing the b2 declaration to

B *const b2 = ...;

alters the behavior. Now b2 is of type "const pointer to B." As before, the const-ness is defined completely by b2's declaration, and is not influenced by its initializer. When you try to assign to this new b2:

b2 = b1;

the compiler correctly perceives the violation of b2's const properties.

Notes

[1] Besides, after last month's columnist exodus, Marc Briand would put a bounty on me if I left!

[2] <http://search.support.microsoft.com/>.

[3] The full-fledged grammar rules appear in subclause 8.3.5 and clause 14.

[4] Quoted from paragraph 5.3.5/5. The Standard defines trivial and non-trivial destructors in subclause 12.4. Many — perhaps most? — classes in "real world" usage have non-trivial destructors.

[5] <http://www.peerdirect.com/resources/gotw062a.html>.

[6] Actually, the const has no net effect on the returned value. create is returning a non-const rvalue. Such an rvalue is not cu-qualified (subclause 3.10/9). Translation: the presence or lack of const doesn't change the rvalue's actual behavior. Even if create's return type were B*, you still couldn't modify the returned rvalue.

Bobby Schmidt is a freelance writer, teacher, consultant, and programmer. He is also an alumnus of Microsoft, a speaker at the Software Development and Embedded Systems Conferences, and an original "associate" of (Dan) Saks & Associates. In other 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.