Microsoft isn't the only C++ compiler vendor, but they manage to occupy Bobby's attention quite a lot this month.
Copyright (c) 1999 Robert H. Schmidt
We came, we saw, we over ate. SD (Software Development) West was fun but tiring. I don't sleep well in hotels, and my roommate one Dan-the-Man Saks kept insisting on 7:30 AM wakeup calls. I spent much of the week feeling like an extra in a George Romero film.
Don't ask me what spiffy neato stuff was on the show floor, since I hardly made it out there. What I did see told me the show may have peaked, at least in its current form. SD West used to be in the San Jose Convention Center, moved to the larger Moscone Center in San Francisco a few years ago, but is returning to San Jose next year.
Assuming I'm right, why is SD contracting? I suspect two causes:
- Other shows siphon off vendors and attendees. In this regard, SD is like traditional TV networks fighting cable: newer and more targeted channels (conferences) steal viewers and sponsors. The big splashy product announcements seem to come at vendor-driven shows like Apple's WWDC and Microsoft's Tech Ed, or at conferences like Embedded Systems that focus on more specific technologies.
- You don't have to attend shows to get access to their information, or to their stars. If you want to contact the C++ heavyweights who speak at SD, just email them, hit their websites, or contract them to visit your company. Just about all the speakers have books out, and many write for magazines; very often, their talks are repackaging of what they've written elsewhere.
These conferences are my virtual office, gathering friends and colleagues I typically see only at these shows. For that reason alone, I hope SD finds a way to adapt and thrive.
Oh, one more thing: at each of these shows, CUJ sponsors a dinner for us columnists. Our next rendezvous is in San Jose this September for the Embedded Systems Conference, and it's my turn to organize the CUJ bacchanalia. If you know of dandy eateries in and around San Jose, please drop me a line.
Privacy Please
Q
Hi Bobby,
I have been a regular reader of your Q&A column in CUJ. I have a question for you. Here is the scenario:
class Base { public: virtual void f(); }; class Derived : public Base { private: virtual void f(); // was public in Base }; Base *b = new Derived; (*b).f(); // OK, calls Derived::f Derived *d = new Derived; (*d).f(); // error, can't access Derived::fWhy does the language allow a private function to be called through the base class pointer?
My compiler is Microsoft's VC++ 5.0 compiler. Is this a bug in the compiler or this is allowed by the language specification for any intended purpose? Please clarify.
Thanks and regards, Anand Sowmithiran
A
VC++ is correct. To see why, we must explore a fundamental difference between static and dynamic typing. (I altered your original example, to better show the underlying problem.)
In your code, *b has the static type Base, while *d has the static type Derived. Those static types are determined by the compiler and never change. That's why we say the types are static, or fixed.
At run time, those same objects take on dynamic types that may differ from their static types. In this case, *d has the same static and dynamic types (Derived), while *b has a dynamic type (Derived) different from its static type (Base). As their name suggests, dynamic types are not fixed, but can change over a program's lifetime.
By your question, you believe access control public, protected, and private is affected by an object's dynamic type. By that reasoning, (*b).f() should fail to compile, since *b's dynamic type is Derived, and f is private in Derived.
However, the compiler doesn't care or even know about dynamic types; it traffics only in static types. Since *b's static type is Base, and since Base::f is public and accessible, the compiler accepts (*b).f() at face value, as a call to a Base member. It's only at run time that *b is actually bound to a Derived object after the compiler has already determined access control.
I'll grant you, this does seem to be an insecure back door into private member access. But if you ponder the fluid nature of dynamic typing, you'll realize access control can't be reckoned any other way. Consider
void func(Base ¶m) { param.f(); // should this compile? } Base base; func(base); Derived derived; func(derived);The dynamic type of param changes over the lifetime of the program. In the first func call param.f() is bound to Base::f, while in the second call param.f() is bound to Derived::f.
Given your reasoning that dynamic type determines accessibility, should the indicated line compile? Put another way, is the f in param.f() public? Private? Both? Does your answer change if I remove either of the calls to func? Or if I make func inline?
If you are Base's author, the solution to all this is simple: make f non-virtual. Then the called f will always match the static type, regardless of the object's dynamic type.
If you can't or won't change Base, then change Derived so that *b can't dynamically reach Derived::f. You do that by preventing Derived::f from overriding Base::f in the first place. In a classic case of Small World Syndrome, another reader stumbled into the very solution to your problem. Read on...
Let's Get Ready to Rumble!
Q
Bobby,
Okay, a small group of us are having a debate over this, and we figured you would be a good referee.
We have a base class like
class Base { public: virtual void f(); //... other stuff };with a virtual function. We want to derive from this base, and provide two versions of the function in the derived class: one that takes no parameters (like the base class function), and one taking extra parameters that the base class function doesn't.
Initially we were going to design something like
class Derived : public Base { public: virtual void f(); // like Base::f virtual void f(int); // new //... maybe other stuff };But then one of the bright guys here thought: hey, we can collapse those first two derived functions into one with
class Derived : public Base { public: virtual void f(int = 0); //... maybe other stuff };This way we can call the derived function just like we do the base function, with no arguments, or we can call it with arguments.
So you're wondering, what's the debate? Well, most of us think this is a slick solution, but one person disagrees. She said she once read about problems combining default arguments and virtual functions, but can't remember the specifics. We have a lunch bet riding on your answer.
Thanks, Donny Brooks
A
I've massaged your code snippets, so that they match those appearing in reader Anand Sowmithiran's question above. This way I can answer both questions at once.
I'm not certain what behavior you expect, but I can probably guess. Given that you've declared Base::f as virtual, you probably expect "normal" polymorphism like this:
Base *b = new Base; (*b).f(); // calls Base::f b = new Derived; (*b).f(); // calls Derived::fIf my assumption is right, you owe your dissenting colleague lunch your code will compile, but it won't run correctly.
For Derived::f to override Base::f, both functions must share the same signature (name and parameter types). Unfortunately for you, the two f's have different signatures: Base::f has no parameters, while Derived::f has one parameter. Instead of overriding Base::f, Derived::f hides it [1].
Because the two classes no longer share overridden functions, the normal virtualization mechanism is short-circuited: (*b).f always references Base::f and (*d).f always references Derived::f, regardless of the objects' dynamic types:
b = new Base; (*b).f(); // calls Base::f b = new Derived; (*b).f(); // still calls Base::f d = new Derived; (*d).f(); // calls Derived::fI can understand your apparent confusion. Even though the functions' parameter sequences are different, their calling sequences can look identical:
(*b).f(); // OK, calls Base::f() (*d).f(); // OK, calls // Derived::f(int) with default argument 0leading you to believe the functions have the same signature.
Here's the kicker: default arguments do not affect a function's signature. The relevant subclause in the C++ Standard (8.3.6/9, "Default arguments") is quite plain:
A default argument is not part of the type of a function.
By implication, the two statements
void f(int); void f(int = 0);declare the same function, while
void f(); void f(int = 0);declare different functions.
Okay Anand, here's your payoff: this same function hiding solves your private access dilemma. Simply declare Derived::f with parameters different from those of Base::f, possibly giving those parameters default arguments. You will then no longer be able to reach Derived::f from a pointer to Base.
This trick can cause untold grief when you aren't expecting it. If you want a function to be overridden, it must have the same signature across classes. As I've shown above, default arguments can fool you into thinking functions are overridden when they really aren't. I've not experienced this particular "gotcha" in my own work, but I imagine it can lead to extremely stealthy bugs.
Hobson's Choice
Q
Dear Bobby,
This email is in response to your article in the June '99 issue of C/C++ Users Journal about the compliance of Microsoft's compiler.
Please note that you can get the VC6 compiler to be compliant on the issue of the for(int i;...;...) by using the compliance switch: /Za. As you have guessed, making this change to the default behavior breaks too much existing code.
Best regards, Fabrice Debry
A
I tested the command-line option /Za on VC++ 5 and 6, and found that
for (int i;;) ; for (int i;;) ;compiles with the option on, but fails to compile with the option off [2]. In the latter case, VC++ complains that i is multiply defined.
According to the command-line help, /Za means "disable extensions." Informally, I've treated the a in /Za as a mnemonic for "ANSI" or "ANSI conformance," and interpreted the switch states thusly:
- /Za on = translate strictly conforming code only
- /Za off = translate strictly conforming code, plus code that has Microsoft-specific extensions
As a result, I thought that /Za affected only code outside the Standard, meaning strictly conforming code was unaffected. Apparently I was wrong. I never would have guessed that disabling a strictly conforming feature qualifies as an "extension" [3].
To be fair, I have sympathy for Microsoft's desire to not break existing code. Consider this variation on the example I published in May:
int i = 42; int main() { int j; for (int i = 0;;) ; j = i; // which i? printf("j = %d\n", j); }With language extensions disabled (/Za on) the run-time result is
j = 42matching the current C++ Standard. But with language extensions enabled (/Za off) the result is
j = 0matching the behavior in Standard C.
In effect, the source code has undergone a silent change: while the source itself is physically unaltered, its run-time behavior is different. Further, since the code continues to build, the program author may be unaware of any change. This is one of those odd and unfortunate cases where the same code will translate and run one way as C and older C++, and another way as newer C++.
So I am sympathetic, yes, but I think Microsoft erred in their solution. Beyond muddying the definition of "extension" as I described above, this solution trades one problem for another: in order to get some conforming features (like for-scoped objects), you must give up others.
To see this, try compiling
#include <string>both with and without /Za. The results are striking: with language extensions enabled the line compiles fine, but with language extensions disabled this single line generates over 100 errors.
P.J. Plauger wrote the Standard libraries Microsoft ships. From what he told me, he got the libraries to compile cleanly with the EDG front end's strict switch, but can't get the same results with VC++'s /Za. As I consider EDG's translator to be the most conforming available, this is strong indication that the problem lies not in Plauger's libraries, but in Microsoft's compiler.
Regardless of the cause, the net effect is clear: you cannot have all of VC++'s strictly conforming features available at once. In this particular example, you can either include Standard library headers, or have for-scoped objects; you cannot have both. For most users this is Hobson's choice, effectively rendering for-scoped objects largely unusable in VC++ programs.
Microsoft has compiler options to specifically enable exception handling and run-time type identification all features of a strictly conforming program. Perhaps they should allow more finely tuned control of other conformance features (like for-scoped objects), by either compiler option or pragma. Alternatively, they could change their compiler so that Standard library headers build with language extensions disabled.
Deja Vu
Q
Here I have a question about C++ (but I think it's more about Visual C++). If I try to compile this code on Visual C++ 5.0:
void main() { char *p(NULL); p = new char[1000]; delete []p; }I get these four error messages:
syntax error : 'constant'
'=' : overloaded function as left operand
cannot delete objects that are not pointers
unrecoverable block scoping errorHowever on other compilers (like CodeWarrior for Macintosh) this is working. If I rewrite the code like this:
typedef char *pChar; void main() { pChar p(NULL); p = new char[1000]; delete []p; }then all is okay.
Is this a problem in Visual C++? Is this in the C++ Standard?
Thanks in advance, Sturm Dorel
A
I'm beginning to think the Editor-in-Chief sold my contract to Windows Developer's Journal, but forgot to tell me.
Sturm, what you have discovered is a long-standing "feature" in VC++. The compiler doesn't like the syntax of direct-initialization for pointers:
char *p(NULL); // error in VC++although it's happy to take the equivalent copy-initialization syntax:
char *p = NULL; // OK in VC++I say syntax but not semantics, for as you discovered, hiding the pointer under a typedef makes the compiler happy.
Assuming Microsoft purposely leaves some features unimplemented to avoid breaking code, I can't fathom what code they risk breaking here. I tried this both with and without the magic /Za switch to no avail, leading me to believe this is a genuine bug, and not a conscious "extension."
Erratica
Two Diligent Readers took issue with my March column item "There Can Be Only One!," which concerned a simple singleton class.
Diligent Reader #1, Robert Allan Schwartz, correctly points out that my class allows cloning of singleton objects and a singleton that allows copying isn't very single. Oops. Where my original sample has
private: X(); ~X();you should change the code to read
private: X() { // ... } X(X const &); X &operator=(X const &); ~X();Declaring an explicit copy constructor prevents the compiler from generating a default constructor. But the singleton class object broker (function Object in the original code) needs to create a single encapsulated X object. For that object to be created, some X constructor must be defined.
Solution: declare both constructors private, so that nobody outside X can call them, but actually define the default constructor, so that X::Object can construct its hidden X object.
Diligent Reader #2, Richard Howells, says my original sample fails to compile with you guessed it VC++ 6. I reduced the sample to
class X { static void f() { static X x; } ~X(); }; // error hereran it through VC++, and got this error message for my trouble:
'X::~X' : cannot access private member declared in class 'X'
Interestingly, if I change either f or x to be non-static, the error goes away. Clearly, then, the problem is not just one of access control. And yes, to be thorough, I tried both with and without /Za no difference.
P.S. I'm hereby declaring a moratorium on VC++ questions and comments for at least a month. We'll have to find someone else to pick on.
Notes
1. The C++ Standard discusses this to death in subclause 10.3 ("Virtual functions").
2. You can toggle this option via the IDE's "Project -> Settings..." menu item. From the resulting "Project Settings" dialog, choose the "C/C++" pane, select "Customize" in the "Category" listbox, then check or clear "Disable language extensions."
3. George Orwell would be proud: War is Peace, Ignorance is Strength, Freedom is Slavery, and now Contractions are Extensions.
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 rschmidt@netcom.com.