473,387 Members | 1,465 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,387 software developers and data experts.

implicit hiding of same-named methods of base class in derived classes - why?

6
This code won't compile (two compilers tried, gcc and VC++, both of recent versions, but I don't remember them exactly):

Expand|Select|Wrap|Line Numbers
  1. class C1
  2. {
  3.     public:
  4.         void M1(int i) {}
  5. };
  6.  
  7.  
  8. class C2: public C1
  9. {
  10.     public:
  11.         // using C1::M1;
  12.         void M1(int i, int j) {}
  13. };
  14.  
  15. int main(int argc, char *argv[])
  16. {
  17.     C2 c;
  18.     c.M1(14);
  19.     c.M1(1, 2);
  20.     return 0;
  21. }
Decommenting the using ... line will make it compilable.

This happens because the C++ compiler assumes you want to redefine the base class'es methods completely, if you use a method with the same name, even if it has a different signature.

This, however, seems to be counter-intuitive to me. If I wanted to redefine a method, I'd do so. But otherwise, why hide a base class'es method away, if there's nothing exactly similar in a derived class?

OOP says that objects of a derived class are a kind of objects of the same class as the base class'es objects, only with additional and/or changed functionality - but I never heard anybody say that objects of publicky derived classes may actually have less functionality than the objects of the base class. It is always assumed that publicly derived classes expand the functionality of the base classe, and don't restrict it.

More than that: it should be always possible to substitute an object of a base class with an object of a derived class. This language feature effectively prevents this:

Expand|Select|Wrap|Line Numbers
  1. // something a cat can scratch
  2. class Scratcheable
  3. {
  4.     public:
  5.         virtual void expressPain();
  6. };
  7.  
  8. // normally, a cat can scratch, itself, when scratching for flees, another cat,
  9. // a dog, a human, or anything else that is scratcheable
  10. class Cat: public Scratcheable
  11. {
  12.     public:
  13.         bool scratch() {} // to scratch itself for flees
  14.         bool scratch(Scratcheable* s) {}
  15.         void expressPain()
  16.         { 
  17.             // hiss or meow
  18.         };
  19. };
  20.  
  21. // a person can always be scratched
  22. class Person: public Scratcheable
  23. {
  24.     public:
  25.         // cuddling a cat gets her into the mood for playing, cats scratch and bite while playing
  26.         void cuddle(Cat* c)
  27.         {
  28.             c->scratch(this);
  29.         }
  30.         void expressPain()
  31.         { 
  32.             // say Ouch!
  33.         };
  34. };
  35.  
  36. // a poor cat which has no paws, and as such cannot scratch
  37. // we hide its scratch methods using the awkward C++ feature hide for this
  38. // this way of hiding stuff is not uncommon - singletons use private
  39. // constructors for making them non-callable from outside
  40. // I use a private scratch method to make all scratch methods unavailable 
  41. // from outside - the language gives me no other idiom for
  42. // making the methods unavailable, although I could override all to return 
  43. // false - but then, the stupidity the language enforces upon 
  44. // me is even greater: I have to override methods just to nop them, which
  45. // is something clearly against sound OOP
  46. class CrippledCat: public Cat
  47. {
  48.     private:
  49.         bool scratch() {};
  50. };
  51.  
  52. int main(int argc, char *argv[])
  53. {
  54.     Person p;
  55.     CrippledCat c;
  56.     // a peson cuddling a crippled cat gets scratched, although a crippled 
  57.     // cat is not supposed to be able to scratch!
  58.     p.cuddle(&c);
  59.     // a crippled cat being angry on a peson cannot scratch it, although 
  60.     // it just did! -- compiler errs on next call
  61.     c.scratch(&p);
  62.     return 0;
  63. }
A person who can cuddle a cat cannot cuddle a crippled cat. (I know that the example is stupid from a conceptual point of view, because it states that scratching _must_ happen when cuddling, so the wrong coupling is in the meaning encoded into the example code, but nevertheless, this example proves the point that an object of a derived class cannot be substituted anymore for an object of the base class due to this language feature.)

It seems reasonable to me to assume that the standards writers had something in mind when they introduced this counter-intuitive-ness. But I don't know what. Can anybody explain it to me?
May 25 '07 #1
11 3022
weaknessforcats
9,208 Expert Mod 8TB
Let's take these in sections:

This happens because the C++ compiler assumes you want to redefine the base class'es methods completely, if you use a method with the same name, even if it has a different signature.
is not true.

C++ has function overloading but you cannot overload between a base class and a derived class. In a class hierarchy, a derived class method with the same name as a base class method is said ro DOMINATE the base class method.

That means a derived object calling the method will call the derived method and further, if the derived object tries to call the base method by having the base arguments, it won't compile becuse of the domination.

In this last case you have you make an explicit call to the base method:

deivedObject.base::themethod(etc..)

Next:
But otherwise, why hide a base class'es method away, if there's nothing exactly similar in a derived class?
Suppose you have Door class. You open the door by using the Open method. There are other methods about the size of the door, the grpahical image of the door, etc.

Now you have a requirement for a Door that opens only with a magic spell. There are various solurtions, but one solution is to derived MagicDoor from Door and redefine the Open method to take a char* that has the magic spell. Then you use Magic Door objects. All of the other Door methods will be inherited.

OOP says that objects of a derived class are a kind of objects of the same class as the base class'es objects, only with additional and/or changed functionality - but I never heard anybody say that objects of publicky derived classes may actually have less functionality than the objects of the base class. It is always assumed that publicly derived classes expand the functionality of the base classe, and don't restrict it.
This is just not true.

A derived class may have more or less functionality than the base class. If the derived class has more functionality than the base class and all you have is a base class pointer to the derived object, you use the Visitor design pattern to gain access to the derived object as a derived object pointer so you can call these additional derived class methods.

In fact, I would say the normal case is to operate with derived objects that have more funcitonality than the base class - and to do this polymorphically by using a base class pointer to these objects AND NO type-casting or RTTI or other silliness.

Then there's the question of interface vs implementation. The interface is the public methods in the base class. The implementation is how those methods are implemented. Normally, you see public virtual functions. This is a common design error. Virtual functions should always be private so as to separate the interface from the implementation. Often the base class has private virtual functions that are overriden by private derived class virtual methods. In effect you call derived methods from the base class. This is called the "Hollywood Principle" (don't call us we'll call you). Once you start doing this, your base class bears little resemblance to your derived class.

More than that: it should be always possible to substitute an object of a base class with an object of a derived class. This language feature effectively prevents this
You can NEVER substitiute an obect of a derived class for an object of the base class. When you do, the derived portion of the object is sliced off and you begin working with an amputee. Unfortunately, the VTBL is still in the remaining portion and when you call one of those methods that need data that has been chopped off the object, you crash.

What is true, is that you can always substitute a derived class POINTER or a REFERENCE for a base class pointer or reference. It must be a pointer or reference or the virtual function mechanism won't work. The underlying principle is the derived object can pose as a base object but this is done by address or reference only.
May 25 '07 #2
Aflj
6
Suppose you have Door class. You open the door by using the Open method. There are other methods about the size of the door, the grpahical image of the door, etc.

Now you have a requirement for a Door that opens only with a magic spell. There are various solurtions, but one solution is to derived MagicDoor from Door and redefine the Open method to take a char* that has the magic spell. Then you use Magic Door objects. All of the other Door methods will be inherited.
Right. But more often I want to derive a WoodenDoor or an IronDoor, and besides inheriting the open() method, also add an open(int sqeakLevel). Instead of this happening automatically, I have to explicitly state this.

On the other hand, if I have written both the Door and the MagicDoor classes, and somebody else has written a Robot class, which has a method openDoor(Door* pDoor), and I pass it a MagicDoor*, it will open the MagicDoor without a spell - the compiler won't complain. What am I missing?

A derived class may have more or less functionality than the base class.
I agree. Still, it's not nice OOP to create derived classes with less functionality. I can think of no case right now when this would be desirable. The Door example you used is on one hand weak (see the stupid Robot above, which ignores the fact that you don't want the MagicDoor opened without a spell), on the other hand you usually want to complicate/enhance the functionality of a Door (see the Doors which squeak above), rather than cripple the base class'es functionality.

I don't mean at all that the case is impossible that you need to cripple the base class. I just think that it is a much less common case compared to the case where you need all that's there in the base class and then some.

You can NEVER substitiute an obect of a derived class for an object of the base class.
Agree. Since I'm used to garbage-collected languages, where you never manipulate objects or pointers to them, but always use references, I use object, pointer to object, reference of object interchangeably - which is of course a complete messup for a C++ programmer. However, I meant pointers.

To summarize:
C++ has function overloading but you cannot overload between a base class and a derived class.
That's exactly my problem: why? Why did the language designers decide this? IMO, you often need/want to overload between a base class and a derived class, instead of re-defining/hiding away all the base class'es methods by simply adding a new, similarly-named method.

However, the language designers were probably much smarter than I am, so they must have had a reason. Can anybody tell me the reason?
May 28 '07 #3
weaknessforcats
9,208 Expert Mod 8TB
Right. But more often I want to derive a WoodenDoor or an IronDoor, and besides inheriting the open() method, also add an open(int sqeakLevel). Instead of this happening automatically, I have to explicitly state this.

On the other hand, if I have written both the Door and the MagicDoor classes, and somebody else has written a Robot class, which has a method openDoor(Door* pDoor), and I pass it a MagicDoor*, it will open the MagicDoor without a spell - the compiler won't complain. What am I missing?
This is a problem with failing to separate the implementation from the interface. You get into this pickle by using public virtual functions. The virtual functions define the implementation, the "how I did it". The interface is the set of public member functions in the base class. None of these member functions should ever be virtual.

You are addressing that "all doors open". It's just that some doors open differently from others. Therefore, in your base class, you just have an Open() method:

Expand|Select|Wrap|Line Numbers
  1. class Door
  2. {
  3.      public:
  4.          void Open();
  5.      private:
  6.           virtual void DoOpen() = 0;
  7.  
  8.      protected:
  9.           Door* theDoor;
  10.  
  11. };
  12.  
  13.  
  14. void Door::Open()
  15. {
  16.      theDoor->DoOpen();
  17. }
  18.  
When you create a specific kind of Door, you store the derived object pointer in the base object for later use. In the case of the MagicDoor, it will be the "this" pointer from the derived object.

Expand|Select|Wrap|Line Numbers
  1. class MagicDoor :     public Door
  2. {
  3.      private:
  4.          void DoOpen(); //base class override
  5.          string theSpell;
  6.      public:
  7.          MagicDoor(string Spell);
  8. };
  9.  
  10. MagicDoor::MagicDoor(string Spell) : theSpell(Spell)
  11. {
  12.      theDoor = this;
  13. }
  14.  
  15. void MagicDoor::DoOpen()
  16. {
  17.       string data;
  18.       char buffer[256];
  19.       cin.getline(buffer, 245);
  20.       data = buffer;
  21.  
  22.      if (data == theSpell)
  23.      {
  24.           cout << "The MagicDoor is open" << endl;
  25.      }
  26.      else
  27.      {
  28.            cout << "The MagicDoor is not open" << endl;
  29.      }
  30. }
  31.  
The main():

Expand|Select|Wrap|Line Numbers
  1. int main()
  2. {
  3.     Door* d = new MagicDoor("Open Sesame");
  4.     d->Open();
  5. }
  6.  
and the door will open only when you enter the magic spell. Check out the Design Patterns book and look up the Template Method pattern.

Which leads to:
That's exactly my problem: why? Why did the language designers decide this? IMO, you often need/want to overload between a base class and a derived class, instead of re-defining/hiding away all the base class'es methods by simply adding a new, similarly-named method.

However, the language designers were probably much smarter than I am, so they must have had a reason. Can anybody tell me the reason?
The reason is function overloading vs domination. Overloading requires the same function name but with different function arguments. This is an overload:

Expand|Select|Wrap|Line Numbers
  1. //the functions are at the same scope level
  2. void function();
  3. void function(int);
  4.  
This is not an overload:

Expand|Select|Wrap|Line Numbers
  1. //functions are at different scope levels
  2. void Door::Open();
  3. void MagicDoor::Open(string spell);
  4.  
because it's the same as:

Expand|Select|Wrap|Line Numbers
  1. float data;
  2.  
  3. void function()
  4. {
  5.      string data;
  6.  
  7.      //No way to get to the float here.
  8. }
  9.  
  10.  
where the local variable name dominates the global variable name. You need scope resolution to get around this:

Expand|Select|Wrap|Line Numbers
  1. float data;
  2.  
  3. void function()
  4. {
  5.      string data;
  6.  
  7.      ::data = 2.5f;    //the global float
  8. }
  9.  
  10.  
The same rule applies to your member functions: The derived class method name dominates the base class method name. To use the base method in the derived class you use the scope resolution operator on the call.
May 28 '07 #4
AdrianH
1,251 Expert 1GB
This is a problem with failing to separate the implementation from the interface. You get into this pickle by using public virtual functions. The virtual functions define the implementation, the "how I did it". The interface is the set of public member functions in the base class. None of these member functions should ever be virtual.

You are addressing that "all doors open". It's just that some doors open differently from others. Therefore, in your base class, you just have an Open() method:

Expand|Select|Wrap|Line Numbers
  1. class Door
  2. {
  3.      public:
  4.          void Open();
  5.      private:
  6.           virtual void DoOpen() = 0;
  7.  
  8.      protected:
  9.           Door* theDoor;
  10.  
  11. };
  12.  
  13.  
  14. void Door::Open()
  15. {
  16.      theDoor->DoOpen();
  17. }
  18.  
When you create a specific kind of Door, you store the derived object pointer in the base object for later use. In the case of the MagicDoor, it will be the "this" pointer from the derived object.

Expand|Select|Wrap|Line Numbers
  1. class MagicDoor :     public Door
  2. {
  3.      private:
  4.          void DoOpen(); //base class override
  5.          string theSpell;
  6.      public:
  7.          MagicDoor(string Spell);
  8. };
  9.  
  10. MagicDoor::MagicDoor(string Spell) : theSpell(Spell)
  11. {
  12.      theDoor = this;
  13. }
  14.  
  15. void MagicDoor::DoOpen()
  16. {
  17.       string data;
  18.       char buffer[256];
  19.       cin.getline(buffer, 245);
  20.       data = buffer;
  21.  
  22.      if (data == theSpell)
  23.      {
  24.           cout << "The MagicDoor is open" << endl;
  25.      }
  26.      else
  27.      {
  28.            cout << "The MagicDoor is not open" << endl;
  29.      }
  30. }
  31.  
The main():

Expand|Select|Wrap|Line Numbers
  1. int main()
  2. {
  3.     Door* d = new MagicDoor("Open Sesame");
  4.     d->Open();
  5. }
  6.  
and the door will open only when you enter the magic spell. Check out the Design Patterns book and look up the Template Method pattern.
Interesting, I probably would have put the open command on the operator. I’ve not heard of the “Hollywood” principal. Sort of like this:
Expand|Select|Wrap|Line Numbers
  1. // door
  2. class Door {
  3. public:
  4.   // Used by anyone
  5.   virtual bool OpenDoor();
  6.   // Used by magicians
  7.   virtual bool CastSpell(int magicPoints, string const & magicWords);
  8.   // Used by thieves
  9.   virtual bool PickLock(int dexterityPoints);
  10. };
  11.  
  12.  
  13. // user
  14. class User {
  15. public:
  16.   virtual OpenDoor(Door& doorToOpen) = 0;
  17. };
  18.  
  19. // robot
  20. class Robot : public user {
  21. public:
  22.   virtual OpenDoor(Door& doorToOpen) {
  23.     if (OpenDoor()) {
  24.       cout << “Door opened.” << endl;
  25.     }
  26.     else {
  27.       cout << “Door locked.” << endl;
  28.     }
  29.   }
  30. };
  31.  
  32. // magician
  33. class Magician {
  34.   enum spells_e {
  35.     //...
  36.     openMagicDoorSpell,
  37.     //...
  38.   };
  39.   std::map<spells_e, string> spells;
  40.  
  41.   int magicPoints; // could be part of user class
  42. public:
  43.   virtual OpenDoor(Door& doorToOpen) {
  44.     if (doorToOpen.OpenDoor()) {
  45.       cout << “Door opened.” << endl;
  46.     }
  47.     else if(doorToOpen.CastSpell(magicPoints, spells[openMagicDoorSpell])
  48.             && doorToOpen.OpenDoor()) {
  49.       cout << “Door opened.” << endl;
  50.     }
  51.     else {
  52.       cout << “Door locked.” << endl;
  53.     }
  54.   }
  55. };
  56.  
This is of course missing a lot, and I’m not sure if I would have the magician automagicly (if you forgive the pun ;)) open the door if it can’t be open by regular means.

A minor issue with this is that the CastSpell() function is available to all. The programmer must restrain themselves from calling it. A failing in the language IMHO; should restrict some functionality based on the calling object. Eiffel has this.


Adrian
May 28 '07 #5
weaknessforcats
9,208 Expert Mod 8TB
Expand|Select|Wrap|Line Numbers
  1. // door
  2. class Door {
  3. public:
  4.   // Used by anyone
  5.   virtual bool OpenDoor();
  6.   // Used by magicians
  7.   virtual bool CastSpell(int magicPoints, string const & magicWords);
  8.   // Used by thieves
  9.   virtual bool PickLock(int dexterityPoints);
  10. };
  11.  
The problem here is that the interface of the Door is not usable by all derived classes. What you have are various methods of opening a Door. The weakness is you have to change this class every time a new way to open a door appears. This is not good when you have a large installed base.

The Door can only Open() because that is the published interface and it must be independent from all derived classes and usable by all derived classes.

I added code for your LockingDoor that has a lock picked by dexterity points.

Expand|Select|Wrap|Line Numbers
  1. /////////////////////////////////////////////////////////
  2.  
  3. //Adrian's Door needs a thief to pick the lock
  4. class Thief
  5. {
  6. public:
  7.     void SetDexterityPoints(int arg);
  8.     int    GetDexterityPoints();
  9. private:
  10.     int DexterityPoints;
  11. };
  12. void Thief::SetDexterityPoints(int arg)
  13. {
  14.         this->DexterityPoints = arg;
  15. }
  16. int Thief::GetDexterityPoints()
  17. {
  18.         return this->DexterityPoints;
  19. }
  20.  
  21. class LockingDoor : public Door
  22. {
  23.      private:
  24.          void DoOpen(); //base class override
  25.          Thief* t;
  26.      public:
  27.          LockingDoor(Thief* t);  //the lockpicker
  28.  
  29. };
  30.  
  31. LockingDoor::LockingDoor(Thief* t) : t(t)
  32. {
  33.      theDoor = this;
  34. }
  35. void LockingDoor::DoOpen()
  36. {    
  37.    int rval = this->t->GetDexterityPoints();
  38.    if ( rval < 10)
  39.    {
  40.      cout << "Not enough to open the door" << endl;
  41.    }
  42.    else
  43.    {
  44.      cout << "Door lock has been picked" << endl;
  45.    }
  46. }
  47.  
  48. int main()
  49. {
  50.     Door* d = new MagicDoor("Open Sesame");
  51.    d->Open();
  52.    Thief Barabbas;
  53.    Barabbas.SetDexterityPoints(5);
  54.    Door* d1 = new LockingDoor(&Barabbas);
  55.    d1->Open();
  56. }
  57.  
If you need to actually use d1 as a LockingDoor* to call added methods on LockingDoor that are not on Door, you need to design for the Visitor design pattern.

Visitor allows you to use a derived object as a derived object when all you have is a base class pointer to the object. And of course, in C++, there is no casting allowed.

Hmmm...Visitor...this may be the signal for my next article. What do you think??
May 28 '07 #6
AdrianH
1,251 Expert 1GB
Oops, forgot to make the Magician derived from User.

I still don’t think that I agree that virtual functions should not be made public, especially when you may want to use a set of objects like a common base type. What reference do you quote from? I’m just taking from my design course from university and how I’ve applied it since, but I dunno, maybe things have changed in the last 7 years.
The problem here is that the interface of the Door is not usable by all derived classes. What you have are various methods of opening a Door. The weakness is you have to change this class every time a new way to open a door appears. This is not good when you have a large installed base.

The Door can only Open() because that is the published interface and it must be independent from all derived classes and usable by all derived classes.
Not exactly true, it doesn’t. Casting a spell doesn’t open the door, it unlocks it. To open the door, you open the door.

And contrary to your comment about it being bad on a large installation base, at least the methods are centralised with the door. Operations on an object should be done by the object. I don’t think I would want to have a User unlock a door in any way at all (though I think I might consider having a lock class). In a game, this may not be critical, but in an OS, you defiantly don’t want things unlocked by just anyone. ;) But like I said, I didn’t put a lot of thought in to this, so there could definitely be holes in it.

I added code for your LockingDoor that has a lock picked by dexterity points.

Expand|Select|Wrap|Line Numbers
  1. /////////////////////////////////////////////////////////
  2.  
  3. //Adrian's Door needs a thief to pick the lock
  4. class Thief
  5. {
  6. public:
  7.     void SetDexterityPoints(int arg);
  8.     int    GetDexterityPoints();
  9. private:
  10.     int DexterityPoints;
  11. };
  12. void Thief::SetDexterityPoints(int arg)
  13. {
  14.         this->DexterityPoints = arg;
  15. }
  16. int Thief::GetDexterityPoints()
  17. {
  18.         return this->DexterityPoints;
  19. }
  20.  
  21. class LockingDoor : public Door
  22. {
  23.      private:
  24.          void DoOpen(); //base class override
  25.          Thief* t;
  26.      public:
  27.          LockingDoor(Thief* t);  //the lockpicker
  28.  
  29. };
  30.  
  31. LockingDoor::LockingDoor(Thief* t) : t(t)
  32. {
  33.      theDoor = this;
  34. }
  35. void LockingDoor::DoOpen()
  36. {    
  37.    int rval = this->t->GetDexterityPoints();
  38.    if ( rval < 10)
  39.    {
  40.      cout << "Not enough to open the door" << endl;
  41.    }
  42.    else
  43.    {
  44.      cout << "Door lock has been picked" << endl;
  45.    }
  46. }
  47.  
  48. int main()
  49. {
  50.     Door* d = new MagicDoor("Open Sesame");
  51.    d->Open();
  52.    Thief Barabbas;
  53.    Barabbas.SetDexterityPoints(5);
  54.    Door* d1 = new LockingDoor(&Barabbas);
  55.    d1->Open();
  56. }
  57.  
I don’t agree with this design because
  • A door doesn’t have a thief. It knows how to open and perhaps how difficult it is to lock/unlock itself, but...
  • A User does operate on the door.
The User can do different operations on it too. Maybe try and break it. If the door is weakened enough, perhaps it will collapse. Because there is a finite number of general things you can do to the door (open, unlock, break, cast spell, hmmm, not sure what else), I think (IMHO) that it is better that the user does it to the door, not the door opens based on the Thief.

Adding to that, it would require that you have an open for each User, thus complicating the door, requiring you to add more pointers to your class as you have defined it. I think that is along similar lines you had against my suggestion (which I don’t exactly agree with) but it is the reason for why I setup the class structure as I did.

I don’t necessarily like the design you suggested, but there are always compromises in design. You just try and get the best that you can. It is usually good to seek second opinions.
If you need to actually use d1 as a LockingDoor* to call added methods on LockingDoor that are not on Door, you need to design for the Visitor design pattern.

Visitor allows you to use a derived object as a derived object when all you have is a base class pointer to the object. And of course, in C++, there is no casting allowed.

Hmmm...Visitor...this may be the signal for my next article. What do you think??
Actually, if I understand it correctly, the Visitor Pattern is what I was suggesting. Also, doesn’t it require that virtual functions are made public (at least in the base class, though if it isn’t public in the derived class, it doesn’t make much sense unless you inherit privately)?

I would be interested in reading your article.


Adrian
May 29 '07 #7
Aflj
6
:o(

Still no answer to my initial question, or I am stupid.

To repeat myself, if you have:

Expand|Select|Wrap|Line Numbers
  1. class C1
  2. {
  3.     public:
  4.         void f1()
  5. }
  6.  
  7. class C2: public C1
  8. {
  9.     public:
  10.         void f1(int);
  11. }
then:

Expand|Select|Wrap|Line Numbers
  1. C2 c;
  2. c.f1()
won't compile. This is normal, and happens because in C++ each class defines its own visibility range, hence same-named methods in derived classes hide away methods in the base class. You need to either unhide the base class methods using the "using <base class>::<base class method name>" syntax, or call them explicitly, using the scope resolution operator - c.C1::f1() in the above example.

However, overloading inside a class happens based on the requirement that no two methods have the same signature. I.e. if I have:

Expand|Select|Wrap|Line Numbers
  1. class C1
  2. {
  3.     public:
  4.         void f1()
  5.         void f1(int);
  6. }
  7.  
  8. class C2: public C1
  9. {
  10.     public:
  11.         // nothing
  12. }
then:

Expand|Select|Wrap|Line Numbers
  1. C2 c;
  2. c.f1()
will compile happily. This is so because for the compiler void f1(int) and void f1() are different signatures. What you're trying to convince me is that the compiler suddenly becomes stupid when you place a similarly named method with a different signature in a derived class, and is not able to distinguish void f1(int) and void f1() anymore, and therefore hides the base class'es f1 from derived classes.

Evidently, this is what happens. But my curiosity is not what happens, how to name or describe what happens, or how to work around what happens. My curiosity is why the designers of the language decided that it should happen the way it does. I.e. why do I get to inherit without complicacies only methods which I don't want to overload in the derived class.

The more I think of it, the more upside down it seems to me.
May 29 '07 #8
AdrianH
1,251 Expert 1GB
:o(

Still no answer to my initial question, or I am stupid.

To repeat myself, if you have:

Expand|Select|Wrap|Line Numbers
  1. class C1
  2. {
  3.     public:
  4.         void f1()
  5. }
  6.  
  7. class C2: public C1
  8. {
  9.     public:
  10.         void f1(int);
  11. }
then:

Expand|Select|Wrap|Line Numbers
  1. C2 c;
  2. c.f1()
won't compile. This is normal, and happens because in C++ each class defines its own visibility range, hence same-named methods in derived classes hide away methods in the base class. You need to either unhide the base class methods using the "using <base class>::<base class method name>" syntax, or call them explicitly, using the scope resolution operator - c.C1::f1() in the above example.

However, overloading inside a class happens based on the requirement that no two methods have the same signature. I.e. if I have:

Expand|Select|Wrap|Line Numbers
  1. class C1
  2. {
  3.     public:
  4.         void f1()
  5.         void f1(int);
  6. }
  7.  
  8. class C2: public C1
  9. {
  10.     public:
  11.         // nothing
  12. }
then:

Expand|Select|Wrap|Line Numbers
  1. C2 c;
  2. c.f1()
will compile happily. This is so because for the compiler void f1(int) and void f1() are different signatures. What you're trying to convince me is that the compiler suddenly becomes stupid when you place a similarly named method with a different signature in a derived class, and is not able to distinguish void f1(int) and void f1() anymore, and therefore hides the base class'es f1 from derived classes.

Evidently, this is what happens. But my curiosity is not what happens, how to name or describe what happens, or how to work around what happens. My curiosity is why the designers of the language decided that it should happen the way it does. I.e. why do I get to inherit without complicacies only methods which I don't want to overload in the derived class.

The more I think of it, the more upside down it seems to me.
See this thread.


Adrian
May 29 '07 #9
Aflj
6
See this thread.


Adrian
Finally! Thanks a lot.

To summarize my understanding of the problem: due to implicit conversions provided by C++, if this hiding mechanism wasn't in place, small changes in a base class, of which the compiler won't even warn, can determine changes in the behavior of derived classes. Am I right?
May 29 '07 #10
AdrianH
1,251 Expert 1GB
Finally! Thanks a lot.

To summarize my understanding of the problem: due to implicit conversions provided by C++, if this hiding mechanism wasn't in place, small changes in a base class, of which the compiler won't even warn, can determine changes in the behavior of derived classes. Am I right?
Yes. Happy coding.



Adrian
May 29 '07 #11
weaknessforcats
9,208 Expert Mod 8TB
This quote:
I still don’t think that I agree that virtual functions should not be made public, especially when you may want to use a set of objects like a common base type. What reference do you quote from? I’m just taking from my design course from university and how I’ve applied it since, but I dunno, maybe things have changed in the last 7 years.
The interface in the base class is public. But none of these functions are virtual. The base class public interface functions call private virtual base class functions. It is these private virtual functions that the derived class overrides.

The problem with public virtual functions in the base class is that these are overriden by the derived class and this also overrides the base class functionality.

Thaat forces you in the derived class to code:

Expand|Select|Wrap|Line Numbers
  1. void Derived::Method()
  2. {
  3.         Base::Method();  //recover base class code
  4.  
  5.         //put derived class behavior here.
  6. }
  7.  
Not all base class functions are pure virtual functions. If they are not, and you override, you lose the code on the base class.

Worse, you can't tell of the call above to Base::Method() should be done before or after your derived class code. Now there is ambiguity. Not good.

Often, base class virtual functions contain code that is common to the derived classes or is used as default behavior if the derived class chooses not to override.

That said, almost all textbooks use public virtual base class methods. The reason: Well, that's how you are supposed to do it. However, after 15 years of using these things, it turns out the public virtual is a bad deal.

Look at it this way: The base class provides the inreface. By overriding the base class funcitons, you are overridieng the published interface and along with it the published interface behavior. Remember, once published, an interface cannot be changed.

I googled "separate implementation from interface" and got 210 hits. There's some reading.

And this:
Adding to that, it would require that you have an open for each User, thus complicating the door, requiring you to add more pointers to your class as you have defined it. I think that is along similar lines you had against my suggestion (which I don’t exactly agree with) but it is the reason for why I setup the class structure as I did.
Not true. If the base class has an Open() that calls a private base DoOpen(). There is no reason why every user needs to override this method:

Expand|Select|Wrap|Line Numbers
  1. class Base
  2. {
  3.     public:
  4.        enum Rcodes {NOT_SUPPORTED, OK, etc...}
  5.        void Open();
  6.     private:
  7.        virtual Rcodes DoOpen();
  8. };
  9.  
  10. Rcodes Base::DoOpen()
  11. {
  12.     return NOT_SUPPORTED;
  13. }
  14.  
If the derived class has no DoOpen(), then the base class behavior prevails and the user is returned NOT_SUPPORTED.

If you overide a base public virtual function tyou would need to put this code in every class where Open() is not supported. Now you have bloat and you require that all derived classes have the same method names.

In real life, rarely do derived classes have the same methods as their base classes. They usually have more methods but those methods are outside the hierarchy interface. To get at them using a base pointer to a derived object requires a Visitor. A Visitor allows you to access these methods and even to appear to add methods to a class without having to chnage the class.

The above example is called a hook. Check out the book Design Patterns by Erich Fromm, et al Addison-Wesley 1994 and look up the Template Method pattern for a discussion of hooks. Also, look up Visitor.

Now to the student:

class C1
{
public:
void f1()
};

class C2: public C1
{
public:
void f1(int);
};

C2 c;
c.f1() <---won't compile
To Aflj: You did not read (or did not understand ) my earlier posts. c.f1() cannot compile becuse there is no method in the class C2 that is f1(). The C2 method is f1(int).

The C2::f1(int) hides the base class function. You do inherit C1::f1() but to call it you have to get around the C2::f2(int). So just use an explicit call:

Expand|Select|Wrap|Line Numbers
  1. c.C1::f1();
  2.  
May 29 '07 #12

Sign in to post your reply or Sign up for a free account.

Similar topics

1
by: Fahd Khan | last post by:
Hi team! While troubleshooting a crash I had while using BitTorrent where the torrent's target file names didn't fall into the ascii range I was playing around in the interpreter and noticed this...
5
by: Amit | last post by:
I am facing a problem while using SQL Server with VB application. Implicit conversion from datatype text to nvarchar is not allowed. Use the convert function to run this query. When i see the...
4
by: StressPuppy | last post by:
(posted this in VB group but then realized I probably should have posted here) I have a TabControl with several TabPages. Upon startup, I only want to show the first TabPage, hiding the rest....
17
by: Jochen Luebbers | last post by:
Hi, I've a problem: I compile modules of embedded SQL (for a dynamic lib) on multiple platforms (AIX,Linux,Windows). They are compiled from the same source code. To be able to provide all...
59
by: Chris Dunaway | last post by:
The C# 3.0 spec (http://msdn.microsoft.com/vcsharp/future/) contains a feature called "Implicitly typed local variables". The type of the variable is determined at compile time based on the...
11
by: Alex | last post by:
Hello all, I have a main form(say "form1") .i want to display another form(say "form2") on occuring of an event (say a button click) and want to hide it after some time so that it will again...
4
by: jaime | last post by:
Hi all. Apologies, since this is more a tool question, than strictly a language question, but hey, it seemed like an appropriate place to ask... I'm a c newbie (and have been now for about 6...
3
by: utab | last post by:
Dear all, I was trying to write a more complex program, and while searching for sth in my reference C++ primer, by Lippman. I had a question in the mind, see the code below #include <string>...
20
by: Bill Cunningham | last post by:
I am stumped on this one. I tried two methods and something just doesn't seem right. I'll try my new syle. #include <stdio.h> #include <stdlib.h> main() { srand(2000); /*seed unsigned */...
162
by: Sh4wn | last post by:
Hi, first, python is one of my fav languages, and i'll definitely keep developing with it. But, there's 1 one thing what I -really- miss: data hiding. I know member vars are private when you...
0
by: taylorcarr | last post by:
A Canon printer is a smart device known for being advanced, efficient, and reliable. It is designed for home, office, and hybrid workspace use and can also be used for a variety of purposes. However,...
0
by: aa123db | last post by:
Variable and constants Use var or let for variables and const fror constants. Var foo ='bar'; Let foo ='bar';const baz ='bar'; Functions function $name$ ($parameters$) { } ...
0
by: ryjfgjl | last post by:
If we have dozens or hundreds of excel to import into the database, if we use the excel import function provided by database editors such as navicat, it will be extremely tedious and time-consuming...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
0
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
1
by: Sonnysonu | last post by:
This is the data of csv file 1 2 3 1 2 3 1 2 3 1 2 3 2 3 2 3 3 the lengths should be different i have to store the data by column-wise with in the specific length. suppose the i have to...
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers,...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.