C/C++ Contributing Editors


Uncaught Exceptions: Phoenix Rising

Bobby Schmidt

Bobby describes various sneaky tricks you can perform with reference parameters, operator->, and other mechanisms — and why you shouldn't.


To ask Bobby a question about C or C++, send email to cujqa@mfi.com, use subject line: Questions and Answers, or write to Bobby Schmidt, C/C++ Users Journal, 1601 W. 23rd St., Ste. 200, Lawrence, KS 66046.

Copyright © 1998 Robert H. Schmidt

Welcome back! From the ashes of "The Learning C/C++urve" arises the phoenix of my new Q&A column. Or maybe I should call it a Q&A&O column, where O == Opinions. Unlike Joe Friday, I'm interested in writing more than Just The Facts. Besides, some of my best reader mail results from brash opinions (remember the Hungarian Notation donnybrook?).

Editor-in-Chief Marc Briand came up with a number of candidate names for this column. Of those I really liked "Uncaught Exceptions." It of course alludes to the C++ language directly; but it also describes a problem that a reader can't solve alone, a problem the reader "throws" out and up, hoping some last line of defense catches and solves it.

As I published last month, please direct your questions or comments to <mailto:cujqa@mfi.com>. If you send them to me directly, they risk getting lost beyond the event horizon of my Inbox. Plus, if I decide one month to follow in Dan Saks's footsteps and go walkabout, someone else can intercept your questions in my absence.

Since I suspect many of you write email in a style too loose for print standards, I may edit your questions for usage, capitalization, and so on. However, I hope to edit your content sparingly, if ever [1]. If you have editorial commentary you want for My Eyes Only, or wish to remain anonymous, please so indicate in your mail. Otherwise your testimony may end up part of the public record, and look where that got Clinton.

Also keep in mind time dilation. From the moment you submit a question until it can appear in print, at least two months will elapse [2]. If you must have an answer straight away, let me know. If I can't get you a quick answer, I'll tell you. In any event, if I'm going to print your question in a column, I'll send you a draft of the answer before I submit that column for publication.

Some questions I'll never answer in print. As with my previous columns, I favor topics that involve Standard C and C++ and don't require my writing a master's thesis. Topics to avoid include:

Finally, to help keep things lively, I may from time to time fabricate questions attributed to blatantly fictitious Diligent Readers. Part of your fun will be figuring out which questions those are, although the tone of the questions and the appropriateness of the "author" names should give them away. I also expect to occasionally trawl Usenet and other online Help Wanted areas for inspiration.

Using Directives vs. MSVC

Q

To keep down conflicts with Microsoft's stuff ("we believe in open standards"... right, as long as they are Microsoft standards) I thought I'd use using here and there to expose part of namespace std:

using std::vector;

I later get a compiler error at the point where I say something like

vector<char *> blah;

Is my using declaration in error? I could not find an example in Stroustrup of how to do it with a template class. (I tried some obvious possibilities like

using std::vector<char *>;

to no avail.)

Microsoft's compiler is well known to have problems with namespaces, but I'm also just starting with this stuff.

Thanks. — Dave Callaway

A

You should be able to write

using std::vector;

and have it work as you expect. That you can't sounds like an error in Microsoft's implementation. For support of this thesis, check out the example from section 14.5.4 paragraph 7 of the C++ Standard:

namespace N
    {
    template<class T1, class T2>
    class A
        {
        }; // primary template
    }
     
using N::A; // refers to the primary template

At least MSVC correctly fails to compile

using std::vector<char *>;

which is forbidden by section 7.3.3 paragraph 5 of the Standard.

I tried a variety of techniques to get around MSVC's problem. The only simple ones I could find were a macro:

#define vector std::vector
     
vector<char *> blah;

or the more encompassing using directive

using namespace std;
     
vector<char *> blah;

Unfortunately, there's no way to say the equivalent of

unusing namespace std;

You are stuck with the using directive's effects until the end of the translation unit. At least you can #undef the macro.

In a slightly more complicated solution, you can inherit a class from std::vector:

template<class T>
class vector : public std::vector<T>
    {
    // ...
    };
     
vector<LPCTSTR> blah;

Life After Death

Q

Should the following code be possible? — Conrad Taylor

A

Your code, distilled down, is

/*
//    C example using malloc/free
*/
int *p1 = malloc(sizeof(*p1));
*p1 = 5;
free(p1);
printf("%d\n", *p1);
     
//
//    C++ example using new/delete
//
int *p2 = new int;
*p2 = 5;
delete p2;
cout << *p2 << endl;

This should compile. It may or may not run; the behavior is undefined. Once you delete or free an object, that object and its storage are conceptually vaporized.

That's "conceptually" — in actual fact, the storage may be intact. Because int objects don't have destructors, you might be "safe" dereferencing the way you've shown: the storage very likely still contains the former value (5 in your example) and has not yet been reused for another object.

But again, this is a happy accident on many implementations, and is far from guaranteed. In a vigorous multi-process environment, or on a resource-constrained embedded system, the storage could be taken away the instant you call delete or free.

This is a common error, and is motivation for eschewing real pointers in C++. With a pointer-like class you can eliminate many sources of such dereferencing problems:

{
auto_ptr<int> p(new int);
*p = 5;
delete p; // error, won't compile
}
// p's destructor calls delete
The standard auto_ptr is not a total solution to this problem, since it assumes *p was allocated by new. You'll need to craft an auto_ptr variant to correctly handle malloc:

{
MallocAutoPtr<int>
p((int *) malloc(sizeof(int)));
*p = 5;
free(p); // error, won't compile
}
// p's destructor calls free

For more on such auto_ptr mutations, see my column "Morte d'Autopointer" in the February 1998 CUJ.

The Grand Illusion

Q

Re "const != Permanent" [3]. In the last paragraph, you write:

This is a good reason to avoid passing pointers. Where possible, I prefer to pass references, thereby avoiding the whole question of object ownership.

You're referring to the convention of using pointers to allude to dynamic resources, right? That's a common and useful convention but, alas, only a convention. The "allusion" quickly turns into "illusion" in this example:

void f1( const X* x ) { delete x; }
void f2( const X& x ) { delete &x; }
     
int main() {
  f1( new X );
  f2( * new X );
}

Here f2( * new X ); may look odd because the example is simplified, but code with the same effect isn't uncommon. Not to start another religious argument, but what f2 does isn't much different from the much-debated style of delete this.

The real answer to "how do we tell the user what f1 and f2 might do," I'm sure we agree, is to document the interface — to explicitly state f2's preconditions, postconditions, and other semantics. I agree that passing by const& should be a pretty strong hint that f2 shouldn't delete its parameter, but I'm uncomfortable with relying on the programmer to infer a function's semantics from its parameter list.

Just curious (and I thought you'd appreciate a change of hecklers; Scott's had his turn). — Herb Sutter

A

As you suggest, my thinking was driven in large part by convention. If I have a function declared as

void f2(X const &x)

the caller can reasonably assume x will stay intact. This can't be guaranteed of course, but few things can be in this language. At some point we have to trust apparent programmer intent and the power of convention and idiom.

In my experience calling

f2(*new X);

is unusual, as is taking a pointer to a reference argument and deleting it. The chances of a user seeing this, let alone being confused and misled by it, are small.

Compare this to

f1(new X);

with a pointer argument; there the convention is more common, and the notion of f1 deleting the pointer is not so strained or incredible.

Also, I was thinking that instead of calling new and delete explicitly, I'd wrap the newed object in a class object [4], avoiding the whole problem:

class MetaX
    {
public:
    ~MetaX()
        {
        delete x;
        }
    MetaX() : x(new X)
        {
        }
private:
    X *x;
    };
     
void f3(MetaX const &);

All the allocation/deletion and pointed-to object ownership would be encapsulated and abstracted away.

I'm in complete agreement regarding thorough documentation of a function's assumptions and effects, and preached that gospel for years at Microsoft. But the sad fact is, I've never known anyone (including me) to consistently document this way. And I have yet to see documentation that didn't deviate over time from the reality being documented. In the end, I trust the code's implied "truth" more than the documentation's, all else held equal.

Maybe what we really need is a literate C++, where the documentation and the code are inextricably linked and can't drift out of phase with each other. I leave that as a pondering for others. (Somehow I bet Stan Kelly-Bootle will weigh in here.)

And as for Scott, he got his karmic reward: I'm beta testing his CD, and (at his request) was brutal in my comments.

Ineffective C++

Q

I knew. I thought everybody did. :-) — Scott Meyers

A

(This is in reference to C++ names with double underscores being reserved for the implementation [5].)

Okay Mr. Smarty Pants, we're now even in the "Shoulda Known Better" department. Glad to see you got your email privileges back; we missed your regular column feature last month.

And hey, how's that CD coming along?

Russian Dolls

Q

Dear CUJ, I came across this C++ wrinkle by accident and can't explain why it works. Can you advise whether this is valid C++ and if so what is going on? The code compiles and executes on Microsoft VC++ 5.0 and returns 99.

struct Data {
    static int data(){ return 99; }
};

static Data s_data;
     
struct Test1 {
    Data* operator->()
        { return &s_data; }
};
 
struct Test2 {
    Test1 operator->()
        { return Test1(); } };
struct Test3 {
    Test2 operator->()
        { return Test2(); } };
struct Test4 {
    Test3 operator->()
        { return Test3(); } };
struct Test5 {
    Test4 operator->()
        { return Test4(); } };
     
#include <iostream>
     
void main(int argc, char ** argv)
    {
    Test5 t;
    std::cout << t->data()
              << std::endl;
    }

Regards. — Paul Kerslake

A

Yes, this is "valid" C++ that should run and produce 99.

Since *t doesn't have a function member called data, you might think t->data() shouldn't even compile. If t were a real pointer, you'd be right, but t is a class object. Don't let the -> fool you — in this example, -> is shorthand for a function call to operator->(), which (like most functions) can return anything.

To remove some layers of indirection, I'm changing the type of t to Test2, although my analysis is still valid for your original question. Here's what's happening:

t->data()

is really

(t.operator->())->data()

t.operator->() yields Test1(). Thus, t->data() is equivalent to Test1()->data(). Similarly,

Test1()->data()

is really

(Test1().operator->())->data()

Test1().operator->() yields &s_data. Thus, Test1()->data() is equivalent to (&s_data)->data(). Because this -> is a real built-in -> on a real pointer,

(&s_data)->data()

is really

(*&s_data).data()

or the reduced

s_data.data()

And that expression yields 99.

If you're still not convinced, change the operator-> member names to f1, f2, and so on. That will make the real chain of operations more obvious in your original example:

cout << t.f5().f4().f3().f2().f1()->data() << endl;

Notes

[1] Your prose content, anyway. Code is another matter. I might edit your sample down to its essence; or my editors might change your formatting to fit the magazine's page layout.

[2] For example, it is now mid-September as I write a column that will appear to U.S. subscribers in mid-November.

[3] "The Learning C/C++urve," C/C++ Users Journal, October 1998.

[4] Think of a private char * inside a string object.

[5] Once again from my October 1998 column.

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.