Pete Becker is Senior QA Project Manager for C++ at Borland International. He has been involved with C++ as a developer and manager at Borland for the past six years, and is Borland's principal representative to the ANSI/ISO C++ standardization committee.
The questions above were taken from various online sources, including the Internet and CompuServe. To ask Pete a question about C or C++, send e-mail to pbecker@wpo.borland.com, use subject line: Questions and Answers; or write to Pete Becker, C/C++ Users Journal, 1601 W. 23rd St., Ste. 200, Lawrence, KS 66046.
Q
Should the following member function fragment work?
void Suicidal::DestroyMe() { delete this; return; }That is, can a member function delete the object that was referenced to invoke it? I added the return to indicate that I know that member data would no longer be defined that's not the question here.Why would I want to do something like the above. Well, an object that models a transient, event-driven state machine has the best information concerning when it should go away. Putting that logic in an outer event cracker is unesthetically lumpy.
Does or should the standard indicate whether or not delete this should be supported? I can see how this might cause some problems unwinding function calls, particularly with exceptions tossed into the stew.
A
The C++ language definition says that you can delete a pointer to any non-const object created with new, subject to a couple of limitations concerning derived classes and virtual destructors. Those limitations are not pertinent to this question, so the answer is yes, delete this is valid, and compilers should support it. But just because it's valid doesn't mean that you should do it at least, not without a great deal of thought about alternatives. delete this is dangerous, and should not be used unless absolutely necessary.
One of the dangers, as you point out, is that you cannot safely access an object's data after that object has been deleted. To ensure that your code won't try to access this data, make DestroyMe return immediately after doing the delete operation. DestroyMe must not return a value that depends in any way on any of the data fields of the deleted object. If you need to return a value that depends on data stored in the object, calculate the value before doing the delete, store it in an auto object, and return that object.
Furthermore, if any member function calls DestroyMe, either directly or indirectly, the same safety rule applies: follow the call to DestroyMe immediately with a return. (It's probably easier to impose a design rule that no member function should call DestroyMe rather than trying to trace all the possible paths to internal calls.)
So far, as we've looked at implementing a suicidal class, we've tried to minimize the risk of damage occuring within its member functions. If you create such a class, however, you should also protect the users of your class from accidents. Users don't expect objects to go away as a result of a member function call that simply isn't how C++ usually works. Users will be surprised by the class's behavior in contexts such as this:
void sample1( Suicidal *sptr ) { sptr->DestroyMe(); sptr->Other(); // probably crashes here }This fragment actually demonstrates the same problem that arises in DestroyMe itself: after the call to DestroyMe the object doesn't exist any more. The pointer to the object cannot be used for any purpose, and, in particular, the call to Other is not guaranteed to work.To alert users to this problem you can use a name that warns of the danger. That's why I've been using the function name DestroyMe: it suggests that something unusual is going on, and might help users of this class figure out why their programs are crashing mysteriously.
Here's a more subtle form of this problem:
void sample2() { Suicidal s; s.DestroyMe(); // crashes here }This code will almost certainly crash, because DestroyMe will try to delete the object s, which was not created with new. This one is very easy to overlook. In fact, if I felt that I had no alternative to creating a suicidal class, I'd make sure no one could create an auto object of that class:
class Suicidal { public: static Suicidal *Create(); void DestroyMe(); private: Suicidal (); Suicidal ( const Suicidal& ); };Since Suicidal's constructors are not accessible, users cannot create objects of this type as auto objects or with new. Instead, users must call Suicidal::Create(). This call differs enough from ordinary methods of creating objects to serve as a warning: something unusual is going on. With any luck this warning will alert users to the dangers that this class presents.I hope I've helped clarify the dangers involved in writing suicidal classes. I share your concern that writing a separate object manager is sometimes "unesthetically lumpy." However, that's the beginning of the inquiry, not the end. "Unesthetically lumpy" is not an engineering criterion. It is an intuitive judgment. Our job as engineers is to use this intuitive judgment as a guide through a careful cost/benefit analysis of the use of this construct. delete this leads to code that crashes mysteriously, that is hard to maintain, and that is hard to use. I think this is one case where our intuition leads us astray.
Q
I'm using Rogue Wave's Tools.h++ class library. This library defines a class called RWOrdered as follows [some details omitted]:
class RWOrdered { public: virtual RWCollectable* append (RWCollectable* a); //... };I'm deriving a class from RWOrdered and I'm using a class derived from RWCollectable with it:
class MyCollectable : public RWCollectable { // ... }; class MyOrdered : public RWOrdered { public: virtual MyCollectable* append(MyCollectable* theInfo); };Now I get the warning:
warning: MyOrdered::append() hides virtual RWOrdered::append()The code itself works. What must I do to avoid receiving this warning?A
Actually, the code does not work; it's just that no one has noticed the problem yet. When you override a virtual function you must provide a function with the same signature. That is, you must provide a function having the same name, the same parameter list, the same const qualifiers, and a compatible return type. In this example, the function append in the derived class does not have the same signature as the function append in the base. The derived class append takes a pointer to MyCollectable, the base class append takes a pointer to RWCollectable. These pointers are of different types, despite the fact that converting pointer-to-MyCollectable to pointer-to-RWCollectable is trivial. Since the types differ, the signatures differ, and the derived class version does not override the base class version. The warning here is telling you, a bit indirectly, that calling append does not give you polymorphic behavior. In code, here's what can happen:
RWOrdered *container = new MyOrdered; MyCollectable *collectable = new MyCollectable; container->append( cellectable );At the call to append the compiler sees that container is a pointer to RWOrdered, so it converts collectable into a pointer to RWCollectable and calls RWOrdered::append, not MyOrdered::append.On the other hand, making the call through a pointer to MyOrdered works as expected:
MyOrdered *container = new MyOrdered; MyCollectable *collectable = new MyCollectable; container->append( collectable );Here, the call to append calls MyOrdered::append. This latest example is probably how most programmers use this class, which explains why no one has noticed the underlying problem. But the problem is still there: the class MyOrdered acts in two different ways, depending on whether you access it through a pointer to MyOrdered or through a pointer to RWOrdered. To fix the problem change the signature of append in the derived class:
class MyOrdered : public RWOrdered { public: virtual MyCollectable* append (RWCollectable* the Info); };This version of append properly overrides the version in RWCollectable, and the container now works polymorphically. Unfortunately, we've now lost some type safety. With the earlier version, a call to append with an object of the wrong type would not work:
MyOrdered *container = new MyOrdered; RWCollectable *collectable = new SomeOtherCollectable; container->append( collectable ); // ????The last line was an error with the old version, but is valid in the new one.When we keep running into things that don't work, we should consider that we may have overlooked a fundamental design problem. That's the case here. RWOrdered can handle any object of type RWCollectable. MyOrdered cannot. It only handles objects of type MyCollectable. We simply cannot use objects of type MyOrdered as if they were objects of type RWOrdered. So, MyOrdered should not publicly inherit from RWOrdered: RWOrdered just doesn't act like MyOrdered's base. Instead, MyOrdered should probably be a wrapper class with a data object of type RWOrdered. It can then delegate to the RWOrdered object for the services that RWOrdered provides, and handle any differences itself, like this:
class MyOrdered { public: MyCollectable* append(MyCollectable* theInfo) { // ... container.append( theInfo ); return theInfo; } private: RWOrdered container; };Yes, it's tedious to duplicate most of the interface to RWOrdered through inline functions in a wrapper class. But that's the right way to design classes that use the services provided by other classes. Don't be fooled by similarity: if the classes really do two different things, as they do in this example, don't use inheritance. It may look convenient, but sooner or later it will get you or your users into trouble.Q
In C++, if I have a class A that contains a class B, how can I call a member function in class A from a member function in class B?
Thanks!
A
Use a pointer or a reference. I know that sounds a bit glib, but there is nothing special about contained objects that gives them access to their containers.
This question often arises when two classes share responsibility for some action. For example, changing the background color of a window requires a call into the windowing system. A class library that abstracts the windowing system ought to have a class that provides this service, among others:
class Window { public: void SetBackgroundColor( Color ); Color GetBackgroundColor() const; };A class like this is usually implemented with a private data member that holds the system's handle for a window. Setting the background color then maps directly to the system call:
void Window::SetBackgroundColor( Color color ) { SetWindowColor( Handle, BACKGROUND, color ); }Once a window has been created, changing its background color requires only a call to this member function:
Window Win; Win.SetBackgroundColor( Red );However, there is another idiom that involves assigning values to window attributes rather than making function calls. To support this idiom, a Window class must contain a data object that controls the background color. When this data object has been added, setting the background color looks like this:
Window Win; Win.BackgroundColor = Red;A natural implementation in C++ is to overload the assignment operator to actually make the system call to change the window's background color. However, to make this call the assignment operator must contain code that somehow gets the handle for the window. In pseudo-code, the assignment operator would look something like this:
void ColorController::operator = ( Color color ) { SetWindowColor( [My Window]->Handle, BACKGROUND, color ); }The problem here lies in deciding how to store the handle and how to get access to it. This kind of problem would be easier to solve efficiently if C++ enabled us to specify that a ColorController only be created as part of a Window, and that the handle it needs in order to make this call can be found in the Window's data. The compiler could then use the definition of Window to figure out how to access the handle, simplifying the programmer's job.C++ did not take this route. The language provided no way to say that ColorController will only be created as part of a Window, so the compiler cannot magically figure out for us how to get to the handle.
Making ColorController a nested class does not help. Many people, when they first encounter nested classes, assume that there is some sort of functional connection between a class and its nested classes. This is not correct. Nesting a class definition inside another class definition does not change the capabilities of either class. Nesting only affects the scope of the nested class's name and the access rights to its name. As long as the name is accessible, we can create objects of that type that are independent of objects of the containing class:
class Outer { public: class Inner { }; }; Outer::Inner inner;No object of type Outer exists here. Nevertheless, this use of Inner is perfectly valid: the class name Outer:: Inner is publicly accessible, so it can be used to create objects. Those newly created objects have no inherent connection to any objects of type Outer, so they have no magic way to access data contained in Outer.Now, that's a language-level answer. But it certainly is useful at times to design a class that can only be created inside some other class and that knows about its container. Let's expand our ColorController class to see how we can do this without asking the compiler for special treatment. A natural design is for ColorController to contain a pointer to a Window, an overloaded assignment operator, and a conversion operator:
class Window; class ColorController { public: ColorController( Window *win ); ColorController& operator= ( Color color ); operator Color() const; private: Window *Win; }; class Window { public: ColorController BackgroundColor; Window() : BackgroundColor(this) {} void SetBackgroundColor( Color ); Color GetBackgroundColor() const; }; inline ColorController::ColorController( Window *win ) : Win(win) {} inline ColorController& ColorController::operator = ( Color color ) { Win->SetBackgroundColor( color ); return *this; } inline ColorController::operator Color() const { return Win->GetBackgroundColor(); }In general this design works fine. The data object simply contains a pointer to the class that it needs to talk to. In this particular case the data object has a couple of limitations. First, ColorController is hard-wired to only control the background color. We could rewrite it so that each instance carried a flag indicating which color it controlled, but this would make the class much more complicated. Second, if your Window class has half a dozen attributes implemented with this scheme you end up with half a dozen identical pointers to the Window that holds them, and half a dozen constructor invocations to initialize these pointers. This is a bit wasteful and quite tedious.You can use a trick to avoid all these duplicate pointers, but it's dangerous. Just remove the pointer from ColorController, and rewrite the member functions to assume that instance of ColorController resides inside a Window.
#define WindowPointer \ ((Win *)(char *)this- \ offsetof(Window, BackgroundColor)) inline ColorController& ColorController::operator = ( Color color ) { WindowPointer->SetBackgroundColor( color ); } inline ColorController::operator Color() const { return WindowPointer->GetBackgroundColor(); }I haven't tested this code. I won't go through the details of how this works, nor will I discuss its limitations, because this is not the kind of trick that anyone should be playing in C++. It's much easier to write member functions to perform the operations that a class needs. If you find that you are contemplating this sort of hacking, take several deep breaths, count slowly to one hundred, and think about how to fix your design. You will find a better way to do it.In general a good C++ design uses member functions to invoke operations on objects. When two or more objects need to communicate, they should do so through pointers or references. If your code winds up carrying around too many pointers it probably means that the interactions between the objects are too complex, which indicates a design problem. In such a case, your question should not be "how do I do this" but "why am I trying to do this." The answer often points to a poor design that needs to be rethought. There's nothing fundamentally wrong with designing objects to call member functions on their containers. But if the cost of the supporting infrastructure becomes prohibitive, it may well be time to change the design and deal with the container directly.