Link to home
Start Free TrialLog in
Avatar of oceanic
oceanic

asked on

Polymorhpism: Is there a better method?

null
Avatar of tma050898
tma050898
Flag of United States of America image

oceanic,

1. What do you mean by "Since you can't declare a pure virtual base class,...". The entire reason for abstract classes is to ensure that derived classes implement all declare pure virtual functions from the base class.

2. You wrote, "At runtime, I figure out whether I need to use a class "B" or class "C", and assign the class "A" pointer to the newly created object's (either a "B" or a "C") address...". This is called "using code to find code" and is an extremely dangerous design. Virtual functions exist so that you don't have to do things like this.

As I understand your problem, here is a very simple example of using an abstract class to implement what amounts to a "function seam".

// Poly.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "iostream.h"

class CDataServer
{
public:
 virtual GetData() = 0;
};

class CODBCServer : public CDataServer
{
public:
 virtual GetData() { cout << "odbc\n"; }
};

class CDAOServer : public CDataServer
{
public:
 virtual GetData() { cout << "dao\n"; }
};

class CDataClient
{
public:
 CDataClient() {};
public:
 CDataServer* m_pServer;
 void GetData()
 {
  if (m_pServer)
  {
   m_pServer->GetData();
  }
 }
};

int main(int argc, char* argv[])
{
 CDataClient client;
 client.m_pServer = new CODBCServer;
 client.GetData();

 return 0;
}

Tom

Avatar of oceanic
oceanic

ASKER

tma, thanks for your reply.  However, there are still a few things to hammer out so we're on the same page.  (it's not always the easiest thing to describe stuff like this)  What I meant by not being able to declare a "pure virtual" base class is that according to C++ rules, you can't declare an *instance* of an abstract base class.  I may not be totally understanding, but it appears you are doing this in your example when you declare a CDataServer object inside your CDataClient class.  Also, I am not doing "code finding".  My code looks much like the contents of your main(), but I have an outer "if" statement that determines what follows the "new" operator.  My "client" is simply behaving differently (only internally), and the type of client is determined by what type of connection needs to be made.  This way -- which I am sure is one of the main reasons C++ offers this -- I don't have to use an "if" statement every time I want to utilize my client connection.

One major difference between your code example and my code is that I am trying to use a "single layer" model -- that is, I am not embedding a server object inside my client object. The client (not "A", but "B" or "C") contains an appropriate other-object that knows all it needs to know about connecting to either of the two types of data sources (servers).  It is similar, but my client or "A" class is the abstract base class, and "B" and "C" have the same function interfaces (so far).  No abstract server class.

Anyway, let me know what you think.

Thanks again,
Oceanic.
oceanic,

>>>...according to C++ rules, you can't declare an *instance* of an abstract base class.  I may not be totally understanding, but it appears you are doing this in your example when you declare a CDataServer object inside your CDataClient class.
=================================
Actually, not being able to instantiate an abstract class means that you can't do this...
 CDataServer* pServer = new CDataServer; // CDataServer is the abstract class

You *can* (as the example shows) do this...
 CDataServer* pServer = new CODBCServer;

>>>This way -- which I am sure is one of the main reasons C++ offers this -- I don't have to use an "if" statement every time I want to utilize my client connection
================================================
In the sample I gave you, I also didn't need an "if" statement. I simply called the m_pServer->GetData. While m_pServer is declared as a CDataServer, it is actually (in this particular case) a pointer to an upcasted CODBCServer object and therefore, the call to m_pServer->GetData resolved to a call to the CODBCServer::GetData virtual function. If m_pServer had been created as a CDAOServer, then the m_pServer->GetData would have resolved to the CDAOServer::GetData virtual function. Because of the way C++ implements polymorphism (via virtual functions) this happens automatically.

Unfortunately, I did not understand your last paragraph insofar as how you architecting your system. Maybe if you could post a simplified version of the code.

Tom

Avatar of oceanic

ASKER

tma, I think we're on the same page.  I don't know everything there is to know about C++, and in fact as an expirement based on your example I went and changed my client class from having virtual functions with bodies that consisted of "return (FALSE);" to = 0 so that they would all be "pure virtual".  I was not expecting it to compile, since I thought you could not declare an instance of such a class (I am declaring a pointer to such an object -- maybe that's the difference). I was very happy when it compiled successfully.  The reason they weren't declared as pure virtual to begin with was because I was getting compiler errors originally (probably due to some other problem), and I suppose I misinterpreted the following quote from the Microsoft on-line documentation:

*** beginning of MS on-line doc quote ***

Pure Virtual Functions and Abstract Classes

C++ Specific ®

An abstract class contains at least one pure virtual function. Specify a virtual function as pure by placing = 0 at the end of its declaration. You don't have to supply a definition for a pure virtual function.

You cannot declare an instance of an abstract base class; you can use it only as a base class when declaring other classes.

END C++ Specific

*** end of MS on-line doc quote ***

That second paragraph is what threw me.  It makes perfect sense though, because the abstract class isn't supposed to do anything function-wise itself.  Anyway, on to an example of what my code is trying to do...

class CMyClient
{
   ...
public:
   CMyClient();
   virtual BOOL Connect(void) = 0;
   virtual Disconnect(void) = 0;
   BOOL GetFirstItemName(CString &strItemName) = 0;
   BOOL GetNextItemName(CString &strItemName) = 0;
   ...
};

class CMyDBClient : public CMyClient
{
   ...
public:
   CMyDBClient(CDatabase *pDatabase);

   BOOL Connect(void);
   void Disconnect(void);
   BOOL GetFirstItemName(CString &strItemName);
   BOOL GetNextItemName(CString &strItemName);
   ...
};

class CMySocketClient : public CMyClient
{
   ...
public:
   CMySocketClient(CString &strNetworkAddress);

   BOOL Connect(void);
   void Disconnect(void);
   BOOL GetFirstItemName(CString &strItemName);
   BOOL GetNextItemName(CString &strItemName);
   ...
};

class CMyDisplayView : public CDisplayView
{
   ...
   CMyDoc *m_pDoc;
   CMyDisplayControl *m_pMyDisplayControl;
public:
   BOOL ShowItems();
};

BOOL CMyDisplayView::ShowItems()
{
   BOOL bStatus;

   (m_pDoc and m_pMyDisplayControl get initialized elsewhere)

   if (m_pDoc->m_bLocalConnect)
      bStatus = m_pMyDisplayControl->ShowItems(&(m_pDoc->m_Database));
   else
      bStatus = m_pMyDisplayControl->ShowItems(m_pDoc->strNetworkAddress);

   return (bStatus);
}

class CMyDisplayControl : public CDisplayControl
{
   ...
   CMyClient *m_pMyClient;
public:
   BOOL ShowItems(CDatabase *pDatabase);
   BOOL ShowItems(CString &strNetworkAddress);
private:
   BOOL ShowItems(BOOL bClearFirst);
};

BOOL CMyDisplayControl::ShowItems(CDatabase *pDatabase)
{
   if ((m_pMyClient = new CMyDatabaseClient(pDatabase)) == NULL)
      return (FALSE);

   return(ShowItems(FALSE));
}

BOOL CMyDisplayControl::ShowItems(CString &strNetworkAddress)
{
   if ((m_pMyClient = new CMySocketClient(strNetworkAddress)) == NULL)
      return (FALSE);

   return(ShowItems(FALSE));
}

BOOL CMyDisplayControl::ShowItems(BOOL bClearFirst)
{
   m_pMyClient->Connect();

   ...
   ...

   return (ShowAllItems());
}

BOOL CMyDisplayControl::ShowAllItems()
{
   CString strName;

   if (m_pMyClient->GetFirstItemName(strName))
   {
      do
      {
         [update display using item names]
      } while (m_pMyClient->GetNextItemData(strName))
   }

   return ( [status] );
}

I don't know how readable the example above is.  It is far simplified, and I typed the whole thing in from the web page (with 8 display lines to work with) :-).  Hopefully I didn't leave anything major out.

Oceanic
Your code looks fine, although I assume that the class CMyDoc and CMyClient are actually the same thing (?)

If this is the case, then you have a textbook case of polymorphism in action, and you have done a good job of designing your classes.

1) Making CMyClient an ABC is a good idea, since you never want to instantiate it, and since you want to force derived classes to re-implement the pure virtuals.

2) Your design might try to move more of the logic into the base class, especially if you find your derived classes keep re-implementing the same routines.  For example, if you wanted to write a GetAllObjectsNames() that filled in a list of CStrings, you could implement this in the base class instead of in the derived classes.  It's hard to come up with a lot of examples on this short example, but you get the idea.  Moving common code up the class heirarchy is a common design goal.

Anyway, it sounds like your design is just what you want...
oceanic,

Sorry about not getting back to you yesterday. I had the kids all day and its a wee bit difficult to type and wrestle with a 4 and 6 year old at the same time <G>.

You obviously have a good handle on what's going on. I think your only problem was reading too much into a poorly written piece of ms documentation per the definition of abstract classes. Now that you understand that you can indeed declare an abstract base class using the technique I showed above, you're on your way because the rest of the code looks good.

Tom

Avatar of oceanic

ASKER

abesoft,

Thanks for the input.  The CMyDoc class is separate from CMyClient, but it is interesting that you bring this up -- I wondered if I could somehow implement my client functionality in CMyDoc.  After all, the document is only opened at the client in this case, and it only relates to client-side information.  Just how to go about that is the question...I am wanting to populate my client display (which could take different forms) based on things in the document, so I need to have a display class real "close" to the document data, but I still need it to be a display class that is re-usable.  Maybe I have tunnel vision on this.  If you can think of a way to do it, and it would fit withing Microsoft's Document-View architecture, please let me know.

Thanks again,
Oceanic
Avatar of oceanic

ASKER

tma,

No problem.  I certainly understand the "having the kids" syndrome :-).  Thanks for your help.  I don't know if you (or abesoft, if you're reading this) also look through the MFC area, but I posted a question there as well (relating to a unified purpose).  These classes we have spoken about are in a DLL; I intend to reuse them from a "complex" of applications that I am writing that work together.  I was hoping that I wasn't doing something really weird and you helped confirm that.  I have written several Win32 (non-MFC) DLLs, but I haven't written an abstract class that lives in a DLL.  Dumb question, is this possible? Maybe it's just tricky?  My hope now is that there is something I am missing and that I can fix the weird issue I am having in one of my DLLs.  (This takes my question here to the next step in problem isolation, so to speak.)

I have some assistance from that group already, but please feel free to take a look. (if you do, please ignore my ranting response to another comment someone made) :-)

Thanks again,
Oceanic

I'm sure you've got the hang of polymorphism, but this is an example that I found to be extremely easy to understand.

class Animal {
public:
   Animal();
   virtual void Eat() = 0;
   virtual void Sleep() = 0;
   // other common functions go here, abstract or not
}

class Tiger : public Animal {
public:
   Tiger();
   virtual void Eat();
   virtual void Sleep();
private:
   // specific implementation
   void EatMeat();
   void SleepCurled();
}

class Giraffe : public Animal {
public:
   Giraffe();
   virtual void Eat();
   virtual void Sleep();
private:
   // specific implementation
   void EatLeaves();
   void SleepStanding();
}

void Tiger::EatMeat() {
   // put the specifics in here
}

void Tiger::SleepCurled() {
   // specifics here
}

void Tiger::Eat() {
   EatMeat();
}

void Tiger::Sleep() {
   SleepCurled();
}





void Giraffe::Eat() {
   EatLeaves();
}

void Giraffe::Sleep() {
   SleepStanding();
}

and so on... until

int main() {
   Animal *a1 = new Giraffe();
   Animal *a2 = new Tiger();

   // a1 sleeps like a Giraffe
   a1->Sleep();

   // a2 eats like a Tiger
   a2->Eat();

   return 0;
}
>> but I haven't written an abstract class that lives in a  DLL.  
>> Dumb question, is this possible? Maybe it's just tricky?

Its is quite possible and there are no tricks.  I do it all the time.
Avatar of oceanic

ASKER

VEngineer,

I haven't seen the animals example before; it does clearly illustrate the idea.  I have a question, though.  The derived classes, Tiger and Giraffe both have their Eat() and Sleep() decalred as virtual.  I noticed that tma also does this in his example code.  I always assumed the reason was as simple as why it is done to the base class: so you can re-define what they are in further-derived classes.  Maybe there is a another reason I am missing?  Just curious...
Avatar of oceanic

ASKER

nietod,

Cool.  Thanks.  The only bad thing about that answer is that it means I have to look elsewhere for the problem in my DLL code.  I'd rather it be a fundamental "code rules" issue than object corruption or some other obscure thing.  Thanks though.
oceanic,

Polymorphism is an object-oriented concept and as such is not C++-specific. Having said that, each language that purports to be object oriented is free to implement polymorphism in their own way with their own syntax.

C++ supports the concept of polymorphism through the use of virtual functions (and late binding). Therefore, your initial assumptions are correct in that a C++ member function is declared as virtual if the writer of the class suspects that a situation may arise where the function may need to be bound to at run-time (dependent on the exact object type).

For example, it is often a misconception that if you want to override a function, that function must be virtual. Actually, it depends on how that function is being called.

Let's say that I derive a class named CDerived from class CBase. Let's say that CBase defines a non-virtual function called foo. I can still override the function foo in CDerived IF I have a pointer to a CDerived object, I cast an CBase object pointer to CDerived or I am calling foo from within another CDerived member function. Everyone of these situations causes the C++ compiler to statically (or early) bind to the derived class' implementation of foo.

HOWEVER, if you have a CDerived object, but have an upcasted pointer to the CBase and attempt to call foo, you will get the CBase::foo instead because the C++ compiler will statically bind to the base class' implementation. Likewise, if a function in the base class calls foo (and foo is not virtual), the base class' foo will be called. Therefore, in these situation you would need foo to be virtual so that C++ would dynamically (or late) bind to the correct version of foo.

Therefore, declaring a function as virtual tells the C++ compiler that the exact version of the function won't be known until run-time when the exact object type is known.

Tom

>>The only bad thing about that answer is that it means I have to look elsewhere
>> for the problem in my DLL code.  I'd rather it be a fundamental "code rules"
>> issue than object corruption or some other obscure thing.  Thanks though.

I read through everything and I didn't see mention of a problem you are having.
nietod,

I think that oceanic's main problem was not understanding the MS dox on abstract classes.
oceanic,
  I believe your question was "is there a better way?"  To the best of my training and understanding, the answer is no.  The way these people are showing you how to do it is the way I was taught in school and how I do it day to day in my job.  It's just a fact of life when using classes and virtual functions that you have to have some base class that has a super-set of functions that do basically nothing.  The classes derived from that base class redefine a sub-set of those functions defined in the base class.
Avatar of oceanic

ASKER

(Hope you all don't mind that I replied to everyone in a single box)

tma,

Thanks for clearly defining the "whats" and "whens" of overriding functions.  I haven't had it spelled out that clearly before.  As many C++ developers have, I came from the C world.  For the most part I have picked up C++ by working with the Microsoft Foundation Classes.  (I know that's not the best way to learn C++) :-)

tma/nietod,

The title of my question said "is there a better way?" because I was wondering if I was using polymorphism optimally, if not erroneously in some way, particularly in relation to MFC (even though I knew I would have to synthesize answers here with MFC information). Part of my intention was to see if there was somehow a better way to implement my functionality, period.  All of the information all have presented suggests otherwise.

nietod,

There have been lots of messages, so my brief mention of the MFC/DLL issue could understandable be missed.  It was in my second comment/reply to tma on June 8.  It starts with "No problem,".

ElmerFud,

Thanks for the comment.  I tend to be a "purest" at times, for lack of a better word, and it was bothering me that I had to create "redundant and useless" functions in my base class.  I know that they are not "redundant and useless", and I don't have a problem with what derivation is (it's a pillar of object orientation) but it is alot of overhead since I don't intend on ever using the base class directly.  If I want to add a function in either derived class, I have to edit two classes (the base and the derived).  I know what I am saying may sound lidicrous to some, but maybe "C+++" would have a way to make this process less verbose.   In a nutshell, I wondered if there was some obscure way to trim down the whole implementation and still meet my goal.

Thanks again to all.
oceanic,

Since it appears that you have the situation under control, what did you want to do with this question?

Tom
Avatar of oceanic

ASKER

Good question (yours).  Well, I was forced by the technicality of not having the question absolutely answered to "reject" your first response; the web page options left me not other course.  This is not what I would have intended, since I knew the question and answer both were going to be further established in subsequent replies.  I had not selected "re-open this question to other experts" before, so I didn't know the "rejection" would be the outcome.

Other than that, I need you to clarify what you mean.  As for me, I have 2 options on the page I am viewing at the moment: 1) increase points or 2) add this comment.  I can't do anything else.

?
Oceanic
If an expert comes close to answering a question, but leaves out something or is not clear or maybe brings up another small questions, then you can submit a comment asking for the additional information or clarification rather than rejecting the answer.  This is actually what happens in a majority of cases.
oceanic,

>>>Other than that, I need you to clarify what you mean.  As for me, I have 2 options on the page I am viewing at the moment: 1) increase points or 2) add this comment.  I can't do anything else.
=================
Actually, your two choices are the following:
1) If you decide that I (or any of the other experts) answered your question, then you can tell that person to submit their comment as an answer and grade them based on their input throughout the thread.

2) If you decide that noone helped you and/or that you figured out the answer on your own, you can delete the question. This might entail posting a question to Linda in the Experts-Exchange forum.

BTW, I think that I best answered your question by explaining what and how polymorphism works, but then again I'm probably not the going to the most objective person to decide that <G>. Seriously, this situation occurs all the time where multiple experts post comments until the poster decides that the question has been answered to his/her satisfaction. When that happens, the poster usually asks the expert to post the comment as an answer.

Tom

Avatar of oceanic

ASKER

tma,

You were the first to respond and you did indeed address the core question of "is there a better way?" with a solid "no.", in the sense that you helped hammer out the small dings in my understanding.  I would like to ask that you submit the comment of your choice :-) as the answer to my question.
Avatar of oceanic

ASKER

nietod, abesoft, VEngineer and ElmerFud,

I know that "thanks" doesn't quite stick as well as points, but I would like to say I appreciate your expert input.  (you may get another chance at a question from me before it's over (-:)  Happy coding!

Oceanic
ASKER CERTIFIED SOLUTION
Avatar of tma050898
tma050898
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of oceanic

ASKER

Thanks tma.