C++ Interfaces (was "Re: Get Allocated Block Size") [0]

"Chris Cranford" <chris.cranford@tkdsoftware.com> wrote in message
 news:wcNNTP.01c34336.24d80e20.1000.1057436717@tkdsoftware.com...
> Well, I opted to use a different approach by having two variables in
the
> dialog:
>
>   DWORD  m_dwObjectType;
>   LPVOID m_lpObjectData;
>
> This way the data is set the LPVOID and the object type is set using a
set
> of constant variables that identify object types.  Then, the dialog can
> recast the LPVOID data to the approach object type structure based on
the
> object type DWORD.  Proved to make more sense....
>
> Does this make more sense to you?
> Chris


Well, I don't know about Joseph [1], but it sure doesn't make sense to
me.

Why do you need to have a pointer to some object declared as LPVOID, in
the first place?

Let me guess that's because you want to receive pointers to objects of
different and unrelated classes, and you then need to differentiate them
when you actually want to use the pointer.

Now, does it make sense for those classes to have a common base class? If
so, you can simply declare the member in your dialog as a pointer to that
base class, and make use of virtual functions in order to get the data or
the operation you need with them, without ever knowing or needing to know
which of the derived classes you are actually using.

Something like this [2]:

class A
{
protected:
   int m_nData;

public:
   A(int nData)
   {
      SetData(nData);
   }

   virtual int GetData() const
   {
      return m_nData;
   }

   virtual void SetData(int nData)
   {
      m_nData = nData;
   }
};

class B : public A
{
protected:
   int m_nDataEx;

public:
   B(int nData, int nDataEx)
   {
      m_nData = nData;
      m_nDataEx = nDataEx;
   }

protected:
   int GetDataEx()
   {
      return m_nDataEx;
   }

   void SetDataEx(int nDataEx)
   {
      m_nDataEx = nDataEx;
   }

public:
   virtual int GetData() const
   {
      return A::GetData() + GetDataEx();
   }

   virtual void SetData(int nData)
   {
      A::SetData(nData - GetDataEx());
   }
};

class YourDialog : public CDialog
{
protected:
   A* m_pA;

public:
   YourDialog(A* pA, CWnd* pParent)
   : CDialog(pParent), m_pA(pA)
   {
      ASSERT(pA != NULL);
   }

   ...

protected:
   void SomeDlgFunction()
   {
      // Get the data
      int nData = pA->GetData();

      // Do something with it
      nData++;

      // Set the data
      pA->SetData(nData);
   }
};

Now, what if in your case A and B can't be one derived from the other or
have a common base class [3]?

You could make your dialog as follows:

class A2
{
protected:
   int m_nData;

public:
   A2(int nData)
   {
      SetData(nData);
   }

   virtual int GetDataA2() const
   {
      return m_nData;
   }

   virtual void SetDataA2(int nData)
   {
      m_nData = nData;
   }
};

class B2
{
protected:
   int m_nData;

public:
   B2(int nData)
   {
      SetData(nData);
   }

   virtual int GetDataB2() const
   {
      return m_nData;
   }

   virtual void SetDataB2(int nData)
   {
      m_nData = nData;
   }
};

class YourDialog2 : public CDialog
{
protected:
   A2* m_pA2;
   B2* m_pB2;

public:
   YourDialog2(A2* pA2, CWnd* pParent)
   : CDialog(pParent), m_pA2(pA2), m_pB2(NULL)
   {
      ASSERT(pA2 != NULL);
   }

   YourDialog2(B2* pB, CWnd* pParent)
   : CDialog(pParent), m_pA2(NULL), m_pB2(pB2)
   {
      ASSERT(pB2 != NULL);
   }
   ...

protected:
   int GetData()
   {
      int nData = 0;
      if (pA2)
         nData = pA2->GetDataA2();
      else if (pB2)
         nData = pB2->GetDataB2();
      else
         ASSERT(FALSE);

      return nData;
   }

   void SetData(int nData)
   {
      if (pA2)
         pA2->SetDataA2(nData);
      else if (pB2)
         pB2->SetDataB2(nData);
      else
         ASSERT(FALSE);
   }


   void SomeDlgFunction()
   {
      // Get the data
      int nData = GetData();

      // Do something with it
      nData++;

      // Set the data
      SetData(nData);
   }
};

Note in this case, the "get" and "set" functions in the unrelated classes
A2 and B2, don't need to have the same names.

However, I wouldn't go for something like this, specially if you need it
for more than two classes, because it forces you to declare a constructor
for each possible class you receive (which may be more than two), you
need
to have a member variable to store each of them, and it forces you to be
constantly doing tests every time you need to access the data. Here's an
even better solution:

class DataInterface
{
public:
   virtual int GetData() const = 0;
   virtual void SetData(int nData) = 0;
};

class A3 : public A2, public DataInterface
{
public:
   A3(int nData) : A2(nData)
   {
   }

   virtual int GetData() const
   {
      return GetDataA2();
   }

   virtual void SetData(int nData)
   {
      SetDataA2();
   }
};

class B3 : public B2, public DataInterface
{
public:
   B3(int nData) : B2(nData)
   {
   }

   virtual int GetData() const
   {
      return GetDataB2();
   }

   virtual void SetData(int nData)
   {
      SetDataB2();
   }
};

Now we reduced the problem to the case when classes "a" and "b" have a
common base class, so you can declare your dialog as:

class YourDialog3 : public CDialog
{
protected:
   DataInterface* m_pData;

public:
   YourDialog3(DataInterface* pData, CWnd* pParent)
   : CDialog(pParent), m_pData(pData)
   {
      ASSERT(pData != NULL);
   }

   ...

protected:
   void SomeDlgFunction()
   {
      // Get the data
      int nData = pData->GetData();

      // Do something with it
      nData++;

      // Set the data
      pData->SetData(nData);
   }
};

Unlike Java and C#, C++ doesn't have support for the interface mechanism,
but this is one way you can implement it. In fact, I have the following
defines in a header file [4]:

/////////////////////////////////////////////////////////////////////////
/
// CppInterfaces.h
/////////////////////////////////////////////////////////////////////////
/

#pragma once

#define Interface class
#define DeclareInterface(name) class name { \
          public: \
          virtual ~name() {} \

#define DeclareBasedInterface(name, base) class name : public base { \
          public: \
          virtual ~name() {} \


#define EndInterface };
#define implements public
/////////////////////////////////////////////////////////////////////////
/

Using these macros, the solution above would be written:

/////////////////////////////////////////////////////////////////////////
/
// IData.h file
/////////////////////////////////////////////////////////////////////////
/

#pragma once

#include "CppInterfaces.h"

DeclareInterface(IData)
public:
   virtual int GetData() const = 0;
   virtual void SetData(int nData) = 0;
EndInterface
/////////////////////////////////////////////////////////////////////////
/

/////////////////////////////////////////////////////////////////////////
/
// A3.h file
/////////////////////////////////////////////////////////////////////////
/

#pragma once

#include "A2.h"
#include "IData.h"

class A3 : public A2
   , implements IData
{
public:
   A3(int nData) : A2(nData)
   {
   }

   virtual int GetData() const
   {
      return GetDataA2();
   }

   virtual void SetData(int nData)
   {
      SetDataA2();
   }
};
/////////////////////////////////////////////////////////////////////////
/

/////////////////////////////////////////////////////////////////////////
/
// B3.h file
/////////////////////////////////////////////////////////////////////////
/

#pragma once

#include "B2.h"
#include "IData.h"

class B3 : public B2
   , implements IData
{
public:
   B3(int nData) : B2(nData)
   {
   }

   virtual int GetData() const
   {
      return GetDataB2();
   }

   virtual void SetData(int nData)
   {
      SetDataB2();
   }
};
/////////////////////////////////////////////////////////////////////////
/

/////////////////////////////////////////////////////////////////////////
/
// YourDialog3.h file
/////////////////////////////////////////////////////////////////////////
/

#pragma once

Interface IData;

class YourDialog3 : public CDialog
{
protected:
   IData* m_pData;

public:
   YourDialog3(IData* pData, CWnd* pParent)
   : CDialog(pParent), m_pData(pData)
   {
      ASSERT(pData != NULL);
   }

   ...

protected:
   void SomeDlgFunction();
};
/////////////////////////////////////////////////////////////////////////
/

/////////////////////////////////////////////////////////////////////////
/
// YourDialog3.cpp file
/////////////////////////////////////////////////////////////////////////
/

#include "stdafx.h"
#include "YourDialog3.h"
#include "IData.h"

....

void YourDialog3::SomeDlgFunction()
{
   // Get the data
   int nData = pData->GetData();

   // Do something with it
   nData++;

   // Set the data
   pData->SetData(nData);
}
/////////////////////////////////////////////////////////////////////////
/

Note the following advantages of this solution over all the previous
ones:

- Your dialog class doesn't need to know *anything* about classes "a",
"b",
  and so on (loose coupling).

- It doesn't even need to include the headers for those classes, thus
won't
  need to be recompiled when they change, as long as you haven't change
the
  IData interface (less file dependencies; shorter builds).

- You can use the same dialog for *any* unrelated object, as long as it
  "implements" the IData interface.

- It's very easy to make *any* class "implement" the needed interface, or
  to create one that does it for it (either by derivation or by
  composition) when the need arises.

In general, using this pseudo-interfaces technique requires some care;
you
need to be aware of some rules, which can't be easily declared to be
enforced at compile time, and so will need to reside in the
methodological
realm [5].

- When declaring a class, use the first base class for "structural"
  inheritance (the "is a" relationship) if there is one, as you normally
do
  (Ej.: CFrameWnd derives from CWnd, CBitmapButton derives from CButton,
  YourDialog derives from CDialog, and so on). This is important if you
are
  deriving from MFC classes; declaring them as the first base class
avoids
  breaking MFC's RuntimeClass mechanism.

- Use additional base classes for interface implementation (as many as
you
  want or need).

- Do not declare any member variables on interface classes. Interfaces
  express behaviour, not data. Besides, this helps avoid some problems if
  you were to use multiple "structural" inheritance and happened to be
  deriving more than once from the same interface class.

- Declare all member functions in interface classes as virtual pure
  (i.e.: with "= 0"). This ensures every instantiable class that declares
  to implement an interface does it for all its functions. It's OK to
  partially implement an interface on an abstract class (in fact it will
  be abstract anyway if you do so) as long as you implement the remaining
  functions in the derived classes you actually intend to instantiate.
  Since the interface classes offer no "basic" implementation [6], you
need
  to be sure everyone that receives a pointer to some interface will be
  able to call any of its members; declaring all your interface members
as
  virtual pure will enforce this at compile time.

- Do not derive your interfaces from anything other than interfaces. You
  can use the DeclareBasedInterface() macro for that. Normal classes can
  choose to implement the basic interface or the derived (extended) one;
  the latter of course means implementing both.

- Assigning a pointer to a class that implements some interface to a
  pointer to that interface requires no casting, as you will be actually
  casting to a base class:

  class CFoo: public CBar
    , implements IDoesSomething;
    , implements IData
  {
  // normal CFoo stuff
  public:
     void SomeFooFunction();
     ...

  // IDoesSomething implementation
  public:
     virtual void DoSomething();

  // IData implementation
  public:
     virtual int GetData() const;
     virtual void SetData(int nData);
  };

  void CalcData(IData* pData)
  {
     ASSERT(pData);

     // calculate some data
     int nData = .......

     pData->SetData(nData);
  }

  void ToDoSomething(IDoesSomething* pDoesIt)
  {
     ASSERT(pDoesIt);
     pDoesIt->DoSomething();
  }

  void SomeOtherFunction(CFoo* pFoo)
  {
     CalcData(pFoo);        // no cast needed
     ToDoSomething(pFoo);   // no cast needed
  }

  Doing it the other way though (from an interface pointer to a pointer
  to the class implementing it), will require an explicit cast, as you
  will be casting to a derived class. Since you will be in fact using
  multiple inheritance (even if for virtually every other practical need
  you can think of it as single inheritance plus interace
implementations),
  these casts can't be the done the "old" way, because they can need
  different actual memory values. However, enabling Run-Time Type
  Information (/GR compiler option) and using dynamic casts works fine
  and it's of course safer anyway. So, instead of doing

  void SomeFunctionReceivingData(IData* pData)
  {
     // Always unsafe cast
     CFoo* pFoo = (CFoo*) pData;

     // Use of unsafe pointer
     // (it may or may not actually point to a CFoo obj [7])
     pFoo->SomeFooFunction();   // may fail in any case
  }

  you could do:

  void SomeFunctionReceivingData(IData* pData)
  {
     CFoo* pFoo = dynamic_cast<CFoo*>(pData);
     if (pFoo)
        pFoo->SomeFooFunction();  // doesn't fail
  }

  In fact the use of dynamic_cast will give you a way to ask any object
or
  interface whether it implements a given interface or not. You can think
  of this as COM's QueryInterface() equivalent in that sense:

  void CalcData(IData* pData)
  {
     ASSERT(pData);

     // calculate some data
     int nData = .......

     pData->SetData(nData);

     // Can I do something else?
     IDoesSomething* pDoesIt = dynamic_cast<IDoesSomething*>(pData);
     if (!pDoesIt)
        return;   // It doesn't; bad luck

     // Ok, let's do it
     pDoesIt->DoSomething();
  }

- You need to be careful to avoid name conflicts between functions in
  different interfaces, as it isn't easy to detect and resolve those
  conflicts if you have a class implementing both.


Notes:
[0] I started to write this post with the intent of given a simple and
    short advise. Given the lengthy post it finally became, I thought
    it would be better posted in a separate thread. I hope all this
    helps someone. Here at work we'd been using this technique since
    long time ago in our MFC and non MFC projects and sure helped us
    a lot [7].
[1] Refers to Joseph M. Newcomer, and actually, I'd bet it doesn't make
    sense to him either. He may have already made a post to say so by
now.
[2] I haven't tried to compile any of the examples; they may contain
    some errors (as usual).
[3] For example, because they are really so unrelated that it doesn't
make
    sense, or because you need to derive each of them from different
    classes already defined in a library, or because they are themselves
    already declared in a lib.
[4] The virtual destructor declaration is there to ensure proper
    destructors will be called in case you happen to call delete on a
    pointer to an interface class.
[5] That compile time enforcement is what languages that support the
    interfaces concept (e.g.: Java, C#) will give you; since C++ doesn't
    have it, you are on you own in that sense. However, it isn't hard to
    do once you get used to it.
[6] If you want to reuse basic interface implementations, you can always
    implement the interface in some class and reuse it either as a base
    class or as a component in other classes.
[7] You couldn't use "pInterface->IsKindOf(RUNTIME_CLASS(CNormalClass))"
    either, because you'll still need to do an unsafe cast to CObject*
    before being able to call IsKindOf().
[8] Actually, this post will also serve as that internal methodology
    document I always wanted to write.
[9] post(643) : warning C4189: '[9]' : local variable is initialized but
    not referenced


-- 
jlr


0
jlr (33)
7/6/2003 4:08:22 AM
vc.mfc 33608 articles. 0 followers. Follow

4 Replies
707 Views

Similar Articles

[PageSpeed] 56

That's the direction I was going to propose, as soon as I found out what he was really
trying to do. This was related to my comment about the solution not making sense.

Derivations, virtual methods, and so on are almost always the correct solution here. I
didn't propose the solutions you give because I didn't want to spend the time until I had
a better idea of what he was trying to accomplish, but you've saved me the trouble.
Thanks. (OP, take a good look at this solution.) 
				joe
On Sun, 6 Jul 2003 01:08:22 -0300, "Jos� Lamas R�os" <jlr@artech.com.uy> wrote:

>"Chris Cranford" <chris.cranford@tkdsoftware.com> wrote in message
> news:wcNNTP.01c34336.24d80e20.1000.1057436717@tkdsoftware.com...
>> Well, I opted to use a different approach by having two variables in
>the
>> dialog:
>>
>>   DWORD  m_dwObjectType;
>>   LPVOID m_lpObjectData;
>>
>> This way the data is set the LPVOID and the object type is set using a
>set
>> of constant variables that identify object types.  Then, the dialog can
>> recast the LPVOID data to the approach object type structure based on
>the
>> object type DWORD.  Proved to make more sense....
>>
>> Does this make more sense to you?
>> Chris
>
>
>Well, I don't know about Joseph [1], but it sure doesn't make sense to
>me.
>
>Why do you need to have a pointer to some object declared as LPVOID, in
>the first place?
>
>Let me guess that's because you want to receive pointers to objects of
>different and unrelated classes, and you then need to differentiate them
>when you actually want to use the pointer.
>
>Now, does it make sense for those classes to have a common base class? If
>so, you can simply declare the member in your dialog as a pointer to that
>base class, and make use of virtual functions in order to get the data or
>the operation you need with them, without ever knowing or needing to know
>which of the derived classes you are actually using.
>
>Something like this [2]:
>
>class A
>{
>protected:
>   int m_nData;
>
>public:
>   A(int nData)
>   {
>      SetData(nData);
>   }
>
>   virtual int GetData() const
>   {
>      return m_nData;
>   }
>
>   virtual void SetData(int nData)
>   {
>      m_nData = nData;
>   }
>};
>
>class B : public A
>{
>protected:
>   int m_nDataEx;
>
>public:
>   B(int nData, int nDataEx)
>   {
>      m_nData = nData;
>      m_nDataEx = nDataEx;
>   }
>
>protected:
>   int GetDataEx()
>   {
>      return m_nDataEx;
>   }
>
>   void SetDataEx(int nDataEx)
>   {
>      m_nDataEx = nDataEx;
>   }
>
>public:
>   virtual int GetData() const
>   {
>      return A::GetData() + GetDataEx();
>   }
>
>   virtual void SetData(int nData)
>   {
>      A::SetData(nData - GetDataEx());
>   }
>};
>
>class YourDialog : public CDialog
>{
>protected:
>   A* m_pA;
>
>public:
>   YourDialog(A* pA, CWnd* pParent)
>   : CDialog(pParent), m_pA(pA)
>   {
>      ASSERT(pA != NULL);
>   }
>
>   ...
>
>protected:
>   void SomeDlgFunction()
>   {
>      // Get the data
>      int nData = pA->GetData();
>
>      // Do something with it
>      nData++;
>
>      // Set the data
>      pA->SetData(nData);
>   }
>};
>
>Now, what if in your case A and B can't be one derived from the other or
>have a common base class [3]?
>
>You could make your dialog as follows:
>
>class A2
>{
>protected:
>   int m_nData;
>
>public:
>   A2(int nData)
>   {
>      SetData(nData);
>   }
>
>   virtual int GetDataA2() const
>   {
>      return m_nData;
>   }
>
>   virtual void SetDataA2(int nData)
>   {
>      m_nData = nData;
>   }
>};
>
>class B2
>{
>protected:
>   int m_nData;
>
>public:
>   B2(int nData)
>   {
>      SetData(nData);
>   }
>
>   virtual int GetDataB2() const
>   {
>      return m_nData;
>   }
>
>   virtual void SetDataB2(int nData)
>   {
>      m_nData = nData;
>   }
>};
>
>class YourDialog2 : public CDialog
>{
>protected:
>   A2* m_pA2;
>   B2* m_pB2;
>
>public:
>   YourDialog2(A2* pA2, CWnd* pParent)
>   : CDialog(pParent), m_pA2(pA2), m_pB2(NULL)
>   {
>      ASSERT(pA2 != NULL);
>   }
>
>   YourDialog2(B2* pB, CWnd* pParent)
>   : CDialog(pParent), m_pA2(NULL), m_pB2(pB2)
>   {
>      ASSERT(pB2 != NULL);
>   }
>   ...
>
>protected:
>   int GetData()
>   {
>      int nData = 0;
>      if (pA2)
>         nData = pA2->GetDataA2();
>      else if (pB2)
>         nData = pB2->GetDataB2();
>      else
>         ASSERT(FALSE);
>
>      return nData;
>   }
>
>   void SetData(int nData)
>   {
>      if (pA2)
>         pA2->SetDataA2(nData);
>      else if (pB2)
>         pB2->SetDataB2(nData);
>      else
>         ASSERT(FALSE);
>   }
>
>
>   void SomeDlgFunction()
>   {
>      // Get the data
>      int nData = GetData();
>
>      // Do something with it
>      nData++;
>
>      // Set the data
>      SetData(nData);
>   }
>};
>
>Note in this case, the "get" and "set" functions in the unrelated classes
>A2 and B2, don't need to have the same names.
>
>However, I wouldn't go for something like this, specially if you need it
>for more than two classes, because it forces you to declare a constructor
>for each possible class you receive (which may be more than two), you
>need
>to have a member variable to store each of them, and it forces you to be
>constantly doing tests every time you need to access the data. Here's an
>even better solution:
>
>class DataInterface
>{
>public:
>   virtual int GetData() const = 0;
>   virtual void SetData(int nData) = 0;
>};
>
>class A3 : public A2, public DataInterface
>{
>public:
>   A3(int nData) : A2(nData)
>   {
>   }
>
>   virtual int GetData() const
>   {
>      return GetDataA2();
>   }
>
>   virtual void SetData(int nData)
>   {
>      SetDataA2();
>   }
>};
>
>class B3 : public B2, public DataInterface
>{
>public:
>   B3(int nData) : B2(nData)
>   {
>   }
>
>   virtual int GetData() const
>   {
>      return GetDataB2();
>   }
>
>   virtual void SetData(int nData)
>   {
>      SetDataB2();
>   }
>};
>
>Now we reduced the problem to the case when classes "a" and "b" have a
>common base class, so you can declare your dialog as:
>
>class YourDialog3 : public CDialog
>{
>protected:
>   DataInterface* m_pData;
>
>public:
>   YourDialog3(DataInterface* pData, CWnd* pParent)
>   : CDialog(pParent), m_pData(pData)
>   {
>      ASSERT(pData != NULL);
>   }
>
>   ...
>
>protected:
>   void SomeDlgFunction()
>   {
>      // Get the data
>      int nData = pData->GetData();
>
>      // Do something with it
>      nData++;
>
>      // Set the data
>      pData->SetData(nData);
>   }
>};
>
>Unlike Java and C#, C++ doesn't have support for the interface mechanism,
>but this is one way you can implement it. In fact, I have the following
>defines in a header file [4]:
>
>/////////////////////////////////////////////////////////////////////////
>/
>// CppInterfaces.h
>/////////////////////////////////////////////////////////////////////////
>/
>
>#pragma once
>
>#define Interface class
>#define DeclareInterface(name) class name { \
>          public: \
>          virtual ~name() {} \
>
>#define DeclareBasedInterface(name, base) class name : public base { \
>          public: \
>          virtual ~name() {} \
>
>
>#define EndInterface };
>#define implements public
>/////////////////////////////////////////////////////////////////////////
>/
>
>Using these macros, the solution above would be written:
>
>/////////////////////////////////////////////////////////////////////////
>/
>// IData.h file
>/////////////////////////////////////////////////////////////////////////
>/
>
>#pragma once
>
>#include "CppInterfaces.h"
>
>DeclareInterface(IData)
>public:
>   virtual int GetData() const = 0;
>   virtual void SetData(int nData) = 0;
>EndInterface
>/////////////////////////////////////////////////////////////////////////
>/
>
>/////////////////////////////////////////////////////////////////////////
>/
>// A3.h file
>/////////////////////////////////////////////////////////////////////////
>/
>
>#pragma once
>
>#include "A2.h"
>#include "IData.h"
>
>class A3 : public A2
>   , implements IData
>{
>public:
>   A3(int nData) : A2(nData)
>   {
>   }
>
>   virtual int GetData() const
>   {
>      return GetDataA2();
>   }
>
>   virtual void SetData(int nData)
>   {
>      SetDataA2();
>   }
>};
>/////////////////////////////////////////////////////////////////////////
>/
>
>/////////////////////////////////////////////////////////////////////////
>/
>// B3.h file
>/////////////////////////////////////////////////////////////////////////
>/
>
>#pragma once
>
>#include "B2.h"
>#include "IData.h"
>
>class B3 : public B2
>   , implements IData
>{
>public:
>   B3(int nData) : B2(nData)
>   {
>   }
>
>   virtual int GetData() const
>   {
>      return GetDataB2();
>   }
>
>   virtual void SetData(int nData)
>   {
>      SetDataB2();
>   }
>};
>/////////////////////////////////////////////////////////////////////////
>/
>
>/////////////////////////////////////////////////////////////////////////
>/
>// YourDialog3.h file
>/////////////////////////////////////////////////////////////////////////
>/
>
>#pragma once
>
>Interface IData;
>
>class YourDialog3 : public CDialog
>{
>protected:
>   IData* m_pData;
>
>public:
>   YourDialog3(IData* pData, CWnd* pParent)
>   : CDialog(pParent), m_pData(pData)
>   {
>      ASSERT(pData != NULL);
>   }
>
>   ...
>
>protected:
>   void SomeDlgFunction();
>};
>/////////////////////////////////////////////////////////////////////////
>/
>
>/////////////////////////////////////////////////////////////////////////
>/
>// YourDialog3.cpp file
>/////////////////////////////////////////////////////////////////////////
>/
>
>#include "stdafx.h"
>#include "YourDialog3.h"
>#include "IData.h"
>
>...
>
>void YourDialog3::SomeDlgFunction()
>{
>   // Get the data
>   int nData = pData->GetData();
>
>   // Do something with it
>   nData++;
>
>   // Set the data
>   pData->SetData(nData);
>}
>/////////////////////////////////////////////////////////////////////////
>/
>
>Note the following advantages of this solution over all the previous
>ones:
>
>- Your dialog class doesn't need to know *anything* about classes "a",
>"b",
>  and so on (loose coupling).
>
>- It doesn't even need to include the headers for those classes, thus
>won't
>  need to be recompiled when they change, as long as you haven't change
>the
>  IData interface (less file dependencies; shorter builds).
>
>- You can use the same dialog for *any* unrelated object, as long as it
>  "implements" the IData interface.
>
>- It's very easy to make *any* class "implement" the needed interface, or
>  to create one that does it for it (either by derivation or by
>  composition) when the need arises.
>
>In general, using this pseudo-interfaces technique requires some care;
>you
>need to be aware of some rules, which can't be easily declared to be
>enforced at compile time, and so will need to reside in the
>methodological
>realm [5].
>
>- When declaring a class, use the first base class for "structural"
>  inheritance (the "is a" relationship) if there is one, as you normally
>do
>  (Ej.: CFrameWnd derives from CWnd, CBitmapButton derives from CButton,
>  YourDialog derives from CDialog, and so on). This is important if you
>are
>  deriving from MFC classes; declaring them as the first base class
>avoids
>  breaking MFC's RuntimeClass mechanism.
>
>- Use additional base classes for interface implementation (as many as
>you
>  want or need).
>
>- Do not declare any member variables on interface classes. Interfaces
>  express behaviour, not data. Besides, this helps avoid some problems if
>  you were to use multiple "structural" inheritance and happened to be
>  deriving more than once from the same interface class.
>
>- Declare all member functions in interface classes as virtual pure
>  (i.e.: with "= 0"). This ensures every instantiable class that declares
>  to implement an interface does it for all its functions. It's OK to
>  partially implement an interface on an abstract class (in fact it will
>  be abstract anyway if you do so) as long as you implement the remaining
>  functions in the derived classes you actually intend to instantiate.
>  Since the interface classes offer no "basic" implementation [6], you
>need
>  to be sure everyone that receives a pointer to some interface will be
>  able to call any of its members; declaring all your interface members
>as
>  virtual pure will enforce this at compile time.
>
>- Do not derive your interfaces from anything other than interfaces. You
>  can use the DeclareBasedInterface() macro for that. Normal classes can
>  choose to implement the basic interface or the derived (extended) one;
>  the latter of course means implementing both.
>
>- Assigning a pointer to a class that implements some interface to a
>  pointer to that interface requires no casting, as you will be actually
>  casting to a base class:
>
>  class CFoo: public CBar
>    , implements IDoesSomething;
>    , implements IData
>  {
>  // normal CFoo stuff
>  public:
>     void SomeFooFunction();
>     ...
>
>  // IDoesSomething implementation
>  public:
>     virtual void DoSomething();
>
>  // IData implementation
>  public:
>     virtual int GetData() const;
>     virtual void SetData(int nData);
>  };
>
>  void CalcData(IData* pData)
>  {
>     ASSERT(pData);
>
>     // calculate some data
>     int nData = .......
>
>     pData->SetData(nData);
>  }
>
>  void ToDoSomething(IDoesSomething* pDoesIt)
>  {
>     ASSERT(pDoesIt);
>     pDoesIt->DoSomething();
>  }
>
>  void SomeOtherFunction(CFoo* pFoo)
>  {
>     CalcData(pFoo);        // no cast needed
>     ToDoSomething(pFoo);   // no cast needed
>  }
>
>  Doing it the other way though (from an interface pointer to a pointer
>  to the class implementing it), will require an explicit cast, as you
>  will be casting to a derived class. Since you will be in fact using
>  multiple inheritance (even if for virtually every other practical need
>  you can think of it as single inheritance plus interace
>implementations),
>  these casts can't be the done the "old" way, because they can need
>  different actual memory values. However, enabling Run-Time Type
>  Information (/GR compiler option) and using dynamic casts works fine
>  and it's of course safer anyway. So, instead of doing
>
>  void SomeFunctionReceivingData(IData* pData)
>  {
>     // Always unsafe cast
>     CFoo* pFoo = (CFoo*) pData;
>
>     // Use of unsafe pointer
>     // (it may or may not actually point to a CFoo obj [7])
>     pFoo->SomeFooFunction();   // may fail in any case
>  }
>
>  you could do:
>
>  void SomeFunctionReceivingData(IData* pData)
>  {
>     CFoo* pFoo = dynamic_cast<CFoo*>(pData);
>     if (pFoo)
>        pFoo->SomeFooFunction();  // doesn't fail
>  }
>
>  In fact the use of dynamic_cast will give you a way to ask any object
>or
>  interface whether it implements a given interface or not. You can think
>  of this as COM's QueryInterface() equivalent in that sense:
>
>  void CalcData(IData* pData)
>  {
>     ASSERT(pData);
>
>     // calculate some data
>     int nData = .......
>
>     pData->SetData(nData);
>
>     // Can I do something else?
>     IDoesSomething* pDoesIt = dynamic_cast<IDoesSomething*>(pData);
>     if (!pDoesIt)
>        return;   // It doesn't; bad luck
>
>     // Ok, let's do it
>     pDoesIt->DoSomething();
>  }
>
>- You need to be careful to avoid name conflicts between functions in
>  different interfaces, as it isn't easy to detect and resolve those
>  conflicts if you have a class implementing both.
>
>
>Notes:
>[0] I started to write this post with the intent of given a simple and
>    short advise. Given the lengthy post it finally became, I thought
>    it would be better posted in a separate thread. I hope all this
>    helps someone. Here at work we'd been using this technique since
>    long time ago in our MFC and non MFC projects and sure helped us
>    a lot [7].
>[1] Refers to Joseph M. Newcomer, and actually, I'd bet it doesn't make
>    sense to him either. He may have already made a post to say so by
>now.
>[2] I haven't tried to compile any of the examples; they may contain
>    some errors (as usual).
>[3] For example, because they are really so unrelated that it doesn't
>make
>    sense, or because you need to derive each of them from different
>    classes already defined in a library, or because they are themselves
>    already declared in a lib.
>[4] The virtual destructor declaration is there to ensure proper
>    destructors will be called in case you happen to call delete on a
>    pointer to an interface class.
>[5] That compile time enforcement is what languages that support the
>    interfaces concept (e.g.: Java, C#) will give you; since C++ doesn't
>    have it, you are on you own in that sense. However, it isn't hard to
>    do once you get used to it.
>[6] If you want to reuse basic interface implementations, you can always
>    implement the interface in some class and reuse it either as a base
>    class or as a component in other classes.
>[7] You couldn't use "pInterface->IsKindOf(RUNTIME_CLASS(CNormalClass))"
>    either, because you'll still need to do an unsafe cast to CObject*
>    before being able to call IsKindOf().
>[8] Actually, this post will also serve as that internal methodology
>    document I always wanted to write.
>[9] post(643) : warning C4189: '[9]' : local variable is initialized but
>    not referenced

Joseph M. Newcomer [MVP]
email: newcomer@flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm
0
newcomer (15975)
7/6/2003 6:37:27 AM
Another approach, which allows you to have a kind of QueryInterface: use
void * as the object pointer and apply dynamic_cast to get an interface
pointer. dynamic_cast is pretty much safe operation, even if applied to
"unrelated" objects. This technique allows you to "mix and match" different
interfaces in your objects. The object doesn't have to have all the
interfaces of interest. If an object doesn't have an interface, dynamic_cast
returns NULL.

I don't recommend, though, to delete an object through a pointer to its
interface (ugly design), unless it's a "dedicated" Delete interface, or some
kind of Release() function.

"Jos� Lamas R�os" <jlr@artech.com.uy> wrote in message
news:%23A01fS3QDHA.2852@tk2msftngp13.phx.gbl...
> "Chris Cranford" <chris.cranford@tkdsoftware.com> wrote in message
>  news:wcNNTP.01c34336.24d80e20.1000.1057436717@tkdsoftware.com...
> [4] The virtual destructor declaration is there to ensure proper
>     destructors will be called in case you happen to call delete on a
>     pointer to an interface class.
> [5] That compile time enforcement is what languages that support the
>     interfaces concept (e.g.: Java, C#) will give you; since C++ doesn't
>     have it, you are on you own in that sense. However, it isn't hard to
>     do once you get used to it.
> [6] If you want to reuse basic interface implementations, you can always
>     implement the interface in some class and reuse it either as a base
>     class or as a component in other classes.
> [7] You couldn't use "pInterface->IsKindOf(RUNTIME_CLASS(CNormalClass))"
>     either, because you'll still need to do an unsafe cast to CObject*
>     before being able to call IsKindOf().
> [8] Actually, this post will also serve as that internal methodology
>     document I always wanted to write.
> [9] post(643) : warning C4189: '[9]' : local variable is initialized but
>     not referenced
>


0
alegr (1131)
7/6/2003 3:07:24 PM
Alexander Grigoriev wrote:

>Another approach, which allows you to have a kind of QueryInterface: use
>void * as the object pointer and apply dynamic_cast to get an interface
>pointer. dynamic_cast is pretty much safe operation, even if applied to
>"unrelated" objects. This technique allows you to "mix and match" different
>interfaces in your objects. The object doesn't have to have all the
>interfaces of interest. If an object doesn't have an interface, dynamic_cast
>returns NULL.

You can't dynamic_cast from void*.

>I don't recommend, though, to delete an object through a pointer to its
>interface (ugly design), unless it's a "dedicated" Delete interface, or some
>kind of Release() function.

Why do you consider it ugly design?

-- 
Doug Harrison
Microsoft MVP - Visual C++
0
dsh (2498)
7/6/2003 7:28:55 PM
"Alexander Grigoriev" <alegr@earthlink.net> wrote in message
news:OIqaRB9QDHA.3236@TK2MSFTNGP10.phx.gbl...
> Another approach, which allows you to have a kind of QueryInterface:
use
> void * as the object pointer and apply dynamic_cast to get an interface
> pointer. dynamic_cast is pretty much safe operation, even if applied to
> "unrelated" objects. This technique allows you to "mix and match"
different
> interfaces in your objects. The object doesn't have to have all the
> interfaces of interest. If an object doesn't have an interface,
dynamic_cast
> returns NULL.

Thanks for your comments.

Using dynamic casts as a way to query interfaces was certainly part of
what I suggested. As Doug Harrison pointed out, you can't do a
dyanmic_cast on a void*, but you never need to do it on a void*, either.
If you created the object yourself, you already have its class, and if
you received a pointer to some object, you either receive it as pointer
to some "normal" class (because you request it to _be_ something) or as
pointer to some interface (because you just need it to _provide some
functionality_). In any of those cases you can query those pointers for
any interface of interest. So, you could add something like this to the
CppInterface.h file:

#define Query_Interface(p, Itf, pItf) ( (pItf = dynamic_cast<Itf*>(p)) !=
NULL)

and use it like this:

void SomeFunction()
{
   CSomeClass sc;
   ISomeItf* pSI;

   if (Query_Interface(&sc, ISomeItf, pSI))
      pSI->SomeItfFunction();
}

or:

BOOL SomeFunction2(CSomeClass* pSC, ISomeItf* pSI)
{
   pSC->SomeClassFunction(pSI->SomeItfFunction());

   IOtherItf* pscOI;
   IOtherItf* psiOI;

   if (Query_Interface(pscOI, IOtherItf, pSC) &&
       Query_Interface(psiOI, IOtherItf, pSI))
   {
       pSI->SomeItfFunction();
       return (pscOI->OtherItfFunction() == psiOI->OtherItfFunction();
   }

   return FALSE;
}

> I don't recommend, though, to delete an object through a pointer to its
> interface (ugly design), unless it's a "dedicated" Delete interface, or
some
> kind of Release() function.

Why do you consider it ugly design?

Could you elaborate on this? I don't see why it would be "ugly design".



-- 
jlr


0
jlr (33)
7/7/2003 1:32:09 PM
Reply:

Similar Artilces:

C++ Interfaces (was "Re: Get Allocated Block Size") [0]
"Chris Cranford" <chris.cranford@tkdsoftware.com> wrote in message news:wcNNTP.01c34336.24d80e20.1000.1057436717@tkdsoftware.com... > Well, I opted to use a different approach by having two variables in the > dialog: > > DWORD m_dwObjectType; > LPVOID m_lpObjectData; > > This way the data is set the LPVOID and the object type is set using a set > of constant variables that identify object types. Then, the dialog can > recast the LPVOID data to the approach object type structure based on the > object type DWORD. Proved to make more sense.......