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:
- My new project is a literal once-in-a-lifetime opportunity. I knew that if I passed it up, I'd likely regret the decision for years.
- I've long held ideas of how Microsoft could improve their business while ethically serving both employees and customers. I now have a chance to test some of those ideas.
- Several members of my new team were marvelously supportive during my personal turmoil last year, even though I was not a fellow employee. I feel more than a little loyalty to them.
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:
- declared in a class nested within a template class
- defined outside the declaring class
- implicitly instantiated
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:
- Declare A as a non-template.
- Declare iter outside A.
- Declare fn outside iter.
- Define fn within the iter class definition (a solution you've already rejected).
- Explicitly instantiate fn.
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,
voidis 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 calledKnowing 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:
- Class type C contains an auto_ptr member pimpl_.
- pimpl_'s destructor directly or indirectly evaluates delete on its owned subobject.
- That owned subobject is of type CImpl.
- At the point where the owned subobject is deleted, Cimpl needs to be a complete type; otherwise the program risks undefined behavior. (The translators flag this risk.)
- CImpl is a complete type only in C.cpp.
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:
- The CUJ article is wrong, for the reasons I show here.
- Between publishing the article and the book, he corrected the error.
- He's raised this issue as his Guru Of The Week item 62 [5].
- He will explain the problem in greater detail in his next book (presumably a sequel to Exceptional C++).
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; // errorWhy 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:
- B is now a struct instead of a class. This change simplifies the B definition, and better represents how this small sample uses B.
- What you call C I call factory, to make the class's purpose more clear.
- create is now a static member. This change obviates the need to create a factory object (which you had done in your main).
- main returns int instead of void, in conformance with the C++ Standard.
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.