1) Multiple inheritance is not replaced by interfaces. An interface is nothing more than an abstract base class where all members are abstract. C++ supports interfaces directly. Multiple inheritance is used in C++ to combine the base implementation of multiple classes into one. Interfaces don't provide any implementation and therefore should not be compared to multiple inheritance.
The best way to understand the difference between an interface and a base class is that an interface defines the contract that an object will implement but does not provide any details about how it will be done. A base class provides the contract and generally some base implementation. An abstract base class where none of the members are implemented is equivalent to an interface.
2) Now in .NET you can have only single inheritance but multiple interface implementations. Why The problem with multiple inheritance is what happens to the base class members (fields specifically) when they are referenced in a derived class. Looking at the defacto inheritance model used by most developers here is a basic class hierarchy.
class BaseClass { public: void PrintName ( ) { printf("%s", m_strName.c_str()); }
protected: string m_strName; };
class Derived1 : public BaseClass { public: Derived1 ( ) { m_strName = "Derived1"; }
void PrintName1 ( ) { PrintName(); } };
class Derived2 : public BaseClass { public: Derived2 ( ) { m_strName = "Derived2"; }
void PrintName2 ( ) { PrintName(); } };
class MultiDerived : public Derived1, public Derived2 { };
int _tmain(int argc, _TCHAR* argv[]) { MultiDerived cls; cls.PrintName(); return 0; }
What name will be printed The answer is...it depends. Because we have two base classes there will be two copies of BaseClass inside each instance of MultiDerived (one for each base class). When we work through the members of Derived1 we will be working with one copy of the base class. When we work through the members of Derived2 we will be working with another, completely separate, copy of the base class. In fact since the compiler can't tell which version you wanted it'll fail to compile.
Changing the method call to PrintName1 will cause Derived1 to be printed but calling PrintName2 would cause Derived2 to be printed. Suddenly the implementation detail of who implemented what method becomes visible to your callers. Not a good thing. You can switch to virtual base classes to resolve some of this confusion. Nevertheless this type of issue can be difficult to debug if you have a deep hierarchy and you happen to be looking in the wrong version of the class.
A counterargument could be that each base class should implement independent functionality and therefore shouldn't share any data but this is rarely the case. MFC classes always derive from CObject. .NET types always derive from Object. If you want independent behavior then aggregation is the better approach.
3) Don't understand this question. It seems like the better question would be what can I no longer achieve in .NET that I could using multiple inheritance The answer here is that you lose the ability to get default implementations of basic functionality contained in multiple classes. However you'll quickly learn that you can achieve the same effect by using aggregation to nest class (what would have been the base classes in C++) instances inside another class and use interfaces to expose the contract that is implemented.
The general practice in .NET is to define an interface to represent the contract for some feature. Then create an abstract base class that provides a default implementation of the interface (some members can remain unimplemented). This allows clients to use the base class whenever they can yet still gives them the freedom of just implementing the interface in cases where they already have a base class or would prefer to defined their own implementation. The collection subsystem along with many other areas of .NET follow this paradigm. Finally note that in public code interfaces are almost always used in lieu of abstract base classes when passing them as parameters or returning them from methods or properties. This gives us the ultimate flexibility without worrying about/limiting the underlying implementation details.
Michael Taylor - 1/19/07 http://p3net.mvps.org
|