patty@chmsr (Patty Jones) (01/19/90)
What does the following 1.36.1 g++ compiler warning mean?? warning: contravariance violation for method types ignored and could this become a serious problem later on as our program grows? Also, I'm not sure I understand what contravariance means -- is that when you pass a pointer to member of class "base" to a function that expects a pointer to a member of class "derived"? But in our situation we have defined a function (SchedEvent; see below) to expect pointers to class "base" and we actually use the SchedEvent function with pointers to class "derived". A bit of background: We are working on an interactive real-time simulation in C++ (in the domain of satellite ground control) using the g++ 1.36.1 compiler on Suns. We have a function that creates event objects which are maintained on an event list; the function prototype is as follows: SchedEvent(int, ActiveSimulationObject *, void_fp, char *) where 1) the first argument is an integer which represents the time the event is scheduled to occur, 2) a pointer to an ActiveSimulationObject object (ActiveSimulationObject is our base class); this points to the object to whom something is going to occur, 3) void_fp is a pointer to a member function in class ActiveSimulationObject (i.e., typedef void (ActiveSimulationObject :: *void_fp) (...); ) 4) the fourth argument is a character string that represents the arguments to the function pointed to by void_fp. Our program works but we are getting the "contravariance" compiler warning on lines where we call SchedEvent. We are calling SchedEvent with pointers to derived classes and their members. For example, part of the class hierarchy we have right now is as follows: ActiveSimulationObject | | SpacecraftComponent | | TapeRecorder and part of the class definition for TapeRecorder is as follows: class TapeRecorder : public SpacecraftComponent { char *mode; // can be "record", "standby", "playback", "fast forward" . . public: void setMode(char *m) { mode = m; } . . }; And finally, we declare: TapeRecorder TR1; To schedule TR1 to go into "record" mode at time 5, we do the following: SchedEvent(5, &TR1, &TR1.setMode, "record"); and as I said, we get the "contravariance" warning on lines such as this where we call SchedEvent. thanks for your help! patty Patty Jones Center for Human-Machine Systems Research School of Industrial and Systems Engineering Georgia Institute of Technology, Atlanta, GA 30332 UUCP: patty@chmsr.UUCP {backbones}!gatech!chmsr!patty INTERNET: patty@chmsr.gatech.edu Patty Jones Center for Human-Machine Systems Research School of Industrial and Systems Engineering Georgia Institute of Technology, Atlanta, GA 30332 UUCP: patty@chmsr.UUCP {backbones}!gatech!chmsr!patty INTERNET: patty@chmsr.gatech.edu
shopiro@alice.UUCP (Jonathan Shopiro) (01/20/90)
In article <5063@hydra.gatech.EDU>, patty@chmsr (Patty Jones) writes: > > What does the following 1.36.1 g++ compiler warning mean?? > > warning: contravariance violation for method types ignored > (In AT&T 2.0, this is an error. Read on to see why.) In C++ you can say that roughly anything you can do to a base class object, you can do to an object of a derived class. In particular, it is generally okay for a member function of a base class to be called for a derived class object. Thus when a function has an argument which is a pointer to a base class, you can pass the address of a derived class object to it, since in the function base class things will be done and that's okay for a derived class object. It is natural (but wrong) to assume that since it's okay to pass the address of a derived class object to a function that expects the address of a base class object, then it must be okay to pass the address of a derived class member function to a function that expects the address of a base class member function. This problem arises in situations like the following. class Base { public: void memf(); }; typedef void Base::MEMF(); // MEMF is the type of Base::memf void apply(Base* bp, MEMF* mfp) { (bp->*mfp)(); } ... Base b; MEMF* p = &Base::memf; apply(&b, p); // invoke b.memf() the hard way So far this is fine. Next define a derived class and try to use apply with it. class Derived : public Base { public: void memf(); }; ... Derived d; apply(&d, &Derived::memf); // ERROR (or warning) The problem with this innocent-looking code is that a member function of a derived class is used where a member function of a base class is expected. The reason this is not okay is that the member function may then be applied to a base object. For example, look at the following code void fake_apply(Base* bp, MEMF* mfp) { Base b; (b.*mfp)(); } This function is type-correct and has the same signature as apply(). If it were called with a member function of class Derived, that function would be invoked for a Base class object. Bad things could then happen, for example suppose class Derived had some extra data members that were not in class Base, and suppose the function munged them. Chaos would follow. The C++ type system attempts to guarantee that this kind of error never happens and that's why the call is not allowed. You can always violate the type system by using casts but casting member function pointers is particularly dangerous and implementation dependent. In the implementations I know about, you will get away with casting a pointer to derived member function to pointer to base member function and then applying it to a derived object if you don't use multiple inheritance and the function is not virtual. I don't know any reason why this shouldn't work in other present or future implementations, but ... Another approach that may be satisfactory is to use virtual functions. If the function Base::memf() above was virtual, then you could rewrite the erroneous call above apply(&d, &Base::memf); // call d.Derived::memf() the hard way This works because &Base::memf is a virtual pointer which is bound when it is invoked and it is invoked for a Derived object. The limitation of this approach is that any function you want to call in the derived class must be declared as a virtual in the base class. If that isn't good enough for you, you could write a virtual function invoke() in the base class with a string or Symbol argument that tells what function to invoke, and then in each derived class override invoke() with a version that looks up the function and calls it. You might be able to think of some other languages that have similar facilities built in [:-)], but you might prefer to do it by hand if method invocation occurs only rarely in your program and you don't want to lose the other advantages of C++. -- Jonathan Shopiro AT&T Bell Laboratories, Warren, NJ 07060-0908 research!shopiro (201) 580-4229