Some words of wisdom for the departing editor, and as usual some priceless C/C++ info for readers.
Copyright © 2001 Robert H. Schmidt
...and then there was one.
Marc Briand has left the building. In exodus he follows Pete Becker, Dan Saks, and P.J. Plauger: all regular monthly writers on board when I started (November 1995 issue), but gone now. I suddenly find myself high in seniority and a tad lonely.
Im going to miss Marc. I always enjoyed doing time with him at trade shows and conferences; but in his new life, I somehow doubt hell have much occasion to attend them anymore. And we never (well, almost never) had disagreements about editorial tone and direction. I hope I wont have trouble training, er, working with his replacement.
This column is my last edited by Marc. As I write, Im not sure who will be topping the masthead next. Someone close to the magazine (not Marc) approached me about taking on a larger role; I had to decline, if only because Microsoft is absorbing quite enough of my time.
I understand why Marcs left. I understand why theyve all left. Magazine writing is marathon running. The trick is to find a pace thats fast enough to win, yet slow enough to sustain. Unfortunately no runner can go forever, and eventually we all drop out of the race.
It lurks in the future mist, quietly waiting: that first day I dont write anymore. Im in no hurry to find it. After all these years, the thrill of seeing my words published hasnt faded. And every time readers tell me they rip open their new issues and plunge straight to my column first my column! Im reminded of why I entered the race [1].
So Marc, I dedicate this column to you. Until we meet again, may you always get your six perfect weeks of Kansas weather, late-spring skies filled with chase-worthy storms. May you finally wake up, look in the mirror, and realize that the 70s are like so over dude. May you laugh with and not at the next editor fool enough to roundup compiler conformance. And may you finally scrape up the courage to ghostwrite Sanford Ds memoirs.
Turn out the lights, the partys over. They say that all good things must end...
Virtual Reality
Q
The code that follows produces (on a Solaris box) the following output when the function f is virtual in A:
ffbef9a0 B::f ffbef9a0 B::fFurthermore, it produces the following output when the function f is non-virtual in A:
ffbef9a3 B::f ffbef9a3 A::fThis result is very confusing to me because in each case we merely have an object of type B namely b.
It would seem to me that since each of our calls involve an object not a pointer of type B, in all cases the B::f method should be invoked. I have added the revelation of the this pointer just to show that indeed we are looking at the same object.
Obviously my reasoning is in error, and I am overlooking something. Could you please explain why the b object invokes the A::f method even though it has inherited the A::do_f method? In other words, please explain the mechanism by which this result is taking place.
Thanks Phil Couchara
A
All four results you report are correct.
The first version of your program, with f virtual, appears as Listing 1. That program shows two calls to f: one direct, one indirect (via do_f):
- The direct call b.f is non-virtual and routes directly to B::f without interrogating the B vtable. b is a B object, not a B pointer or reference. Theres no possibility for the static and dynamic types of b to differ, so theres no need for the extra overhead of a vtable lookup and dispatch. (By implication, if the call did take a detour through the vtable, it would still land in B::f anyway.)
- The indirect call of f via b.do_f is virtual. That call is implicitly (*this).f within do_f. As the dynamic type of *this may differ from its static type, the vtable lookup is required. That lookup shows *this to have dynamic type B; correspondingly the call (*this).f routes to B::f.
Now change your program so that f is non-virtual. This new program shows the same two calls to f. But because f is non-virtual, there is no run-time vtable to resolve calls to f. Instead the compiler reckons which f to call based on static-type context.
The compiler reaches its conclusions thusly:
- B declares its own f, which hides the inherited A::f. The call b.f has only one possible static resolution: B::f.
- B does not declare its own do_f, but instead inherits A::do_f. That function internally calls f. Because that f is not virtual, A::do_f resolves the call statically. The only possible match, from the perspective of A::do_f, is A::f.
In both cases, f resolves based on the static context of the caller. The actual run-time type of the calling object is irrelevant, as no virtual dispatch is involved or even possible.
Bottom line:
- If a function is non-virtual, calls resolve via static type information only, regardless of intuition.
- If a function is virtual, some calls resolve via static type information, and some via dynamic type information. It all depends on who makes the call and how they make it.
Chain Letter
Q
Hello Bobby,
My name is Mohammed, and I am a const reader of the CUJ, and especially the Q&A part. Great job! I extend my greeting to you and your friends making this publication available to us C++ users.
I am not an experienced C++ developer, but I am making the effort to be. I have some questions about C++ and thus am writing to you.
Question 1: When overloading the operators << and >> to support I/O on our own classes, why do we return a reference to istream and ostream?
Question 2: Why do we return a reference to this when overriding the assignment operator?
I know you may say consult a C++ book like ..., and I did read some books, but I still did not get a clear idea about my questions.
Thanks very much Mohammed Abu Hajjleh
A
My first const reader Dan Saks will be proud!
The answer to both questions is quite simple: by returning these references, the operations can be chained together:
X a, b, c; a = b = c; cout << a << b << c; cint >> a >> b >> c;Ill explain the case of assignment first and generalize from there.
The statement
a = b = c;groups as if it were written
a = (b = c);Assuming X is a class type above, and assuming operator= is the typical self-assignment declared as
X &operator=(X const &)then the chained expression is really the nested function call
a.operator=( b.operator=(c) );The inner call
b.operator=(c)takes c as its argument. c has type X, which converts nicely to the operator= parameter type X const &. No drama there.
The outer call
a.operator=( b.operator(c) );takes the result of b.operator(c) as its argument. Because operator= has a return type of X &, the result of b.operator(c) is an X &. That X & result converts to the X const & parameter type a.operator= requires.
In sum: by being declared to return the same type it accepts, operator= can form chained calls to itself.
All of this implies that b.operator= returns a reference to some X object but which one?
Remember that
a = b = csets the state of b from c before setting the state of a from b. The X returned by b.operator= is therefore the state of b after its been set. Implication: b.operator= returns b, which is conveniently available in operator= as *this and is yielded via return *this.
And thats the story of why operator= returns *this.
Counter-argument: The entire expression a = b = c eventually returns a. That value is not further chained, but is silently tossed away. If you never intend to chain assignment this way, declare operator= to return void, thereby sparing the overhead of wasted return values.
This same basic reasoning applies to << and >>, except the associativity is in the other direction, and the operators are very frequently chained. For example, the sequence
cout << a << b;is tantamount to
(cout << a) << b;where << is a call to operator<<. Depending on the types of a and b, the operator<< call is either a member of cout with one parameter:
( cout.operator<<(a) ).operator<<(b);or a non-member function with two parameters:
operator<<( operator<<(cout, a), b);In the former case, operator<< returns *this, which is the stream cout. In the latter case, operator<< returns its first parameter, which is also the stream cout. Both net out to the same effect: each operator<< result is passed down the chain to the next call, as either the value of *this or the first argument.
Oh Say Can You C?
Q
Bobby,
Can you please tell me if there is a mechanism that allows ANSI C programs to access/call functions that have been compiled in C++ modules or libraries. I know that the reverse is possible using the extern "C" declaration.
I have an immediate need to access a C-like API that is only available as C++ generated libraries. The project that I am working on consists of a slew of ANSI C modules that will not compile/run correctly as C++ code without a significant amount of rework.
Regards Nabil Dajani
A
Because youre asking about C calling into some other languages library, the question falls under the jurisdiction of the C Standard.
The only specific library the Standard governs is the Standard C library. The Standard tolerates other libraries and externally-linked entities that follow the C language rules. In all cases, the Standard specifies only the observable behavior of external code. The actual code implementation could be written in any language.
Thats the theory. In practice, every C compiler has its own implementation peculiarities. Your prebuilt C++ library may be callable from one C compiler and not from another. As the C++ Standard puts it [2]:
Linkage from C++ to objects defined in other languages and to objects defined in C++ from other languages is implementation-defined and language-dependent. Only where the object layout strategies of two language implementations are similar enough can such linkage be achieved.
There are so many considerations that I can name but a few:
- type size
- parameter-passing order
- return-value mechanism
- stack alignment
- naming compatibility
- which library functions throw exceptions
- which library functions are instanced, and which are static
- how this pointers are implemented
- which library functions are virtual, and which are non-virtual
- how the virtual dispatch mechanism is implemented
If you cant answer these and similar questions, youre probably out of luck.
But if you can answer the questions, there is hope: you can write a thin layer of new code that maps the C++-specific behavior of the library into a C-compatible form. You declare functions in this layer extern "C" and call them from your C code as if they were C.
While I could literally write an entire book on this topic, youll have to settle for the tiny example I show in Listing 2. The example wont build; its meant for demonstration purposes only. It is also quite limited. In real life, the layer will probably require a combination of C++ and assembly language.
(By the way, the example shows a mangled C++ name. That name is the one produced by Metrowerks CodeWarrior Pro 6, although the idea and issue apply to other compilers.)
Simple Simon
Q
Simple question: In 2001, is there any reason to use C over C++?
Thanks in advance M. Thomas Kent
A
Simple answer: yes.
More complex answer: yes, if you
- cant get a decent C++ compiler for your target system
- already have a C compiler and dont want to invest in a C++ compiler
- have a lot of C code that you cant or wont port to C++
- work in a C shop that cant or wont retrain programmers
- need the new features in C99 that C++ doesnt support
Most yes answers are based on business practicality. If you discount those considerations, and ask the question based on language design and ability only, the answer is almost always no.
Notes
[1] Getting free press passes to conferences and trade shows, with all the perks they entail, isnt a bad motivation either. The only real drag comes at shows with press-averse sponsors and attendees. My worst experience, ironically enough, came at a Microsoft-sponsored conference. When I wore my distinct red press badge, the good people of Redmond shunned me like Hester Prynne.
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.