Why Does C++ Not Let Baseclasses Implement a Derived Class' Inherited Interface

Why can't I use inheritance to implement an interface in C++?

struct B is implementing a function that happens to be called foo and takes no arguments. That function has obviously absolutely no relation to A::foo. So when struct C derives from both A and B, it ends up having inherited:

  • the responsibility to provide an implementation for A::foo
  • a method B::foo that has the same name and signature as A::foo, but is not related to it in any way

I think the problem is obvious. You are trying to use A as the equivalent of a C# interface, but there is no way to express that concept in C++.

Now the using directive also does not help here because all it does is bring B::foo into the scope of C, meaning that it tells the compiler it should consider B::foo as a candidate for resolving the name foo when the latter is encountered inside class C. Unfortunately, that's also unrelated to the responsibility of implementing pure virtual methods.

C++ What to do when a derived class doesn't implement a function from interface

DoT stands for Damage over Time, right? So I imagine it is an object with stats for the damage over time, and it has attributes the amount of damage and the amount of time.

Option 1: Null object

Let the Weapon class have a getDoT method. Weapons that don't have DoT could still return a DoT object with zero and/or neutral values for all of its fields. The code that uses the DoT and knows what those fields are can detect this condition with an if statement and do the proper thing (e.g. if the damage amount is zero, then return before having any effect).

This would be an example of the Null Object pattern.

Option 2: Predicate method

Let the Weapon class have a hasDoT method that returns a bool and a getDoT method that returns the DoT. You said this is what your code currently looks like. I know it is not a popular solution, but it is easy to understand and it gets the job done, so you shouldn't be embarrassed about it.

I would probably add an assertion or throw an exception to catch the situation where getDoT is called on a weapon that does not have DoT.

Option 3: Abstract class

Put the getDoT method in an abstract class. This class can either be its own thing or it could be a subclass of Weapon, depending on what makes sense for your application. You can use some form of dynamic casting to convert from a Weapon pointer to a pointer of the right type. Other answers have more details about how to do this.

This is actually the least flexible option because it assumes that we know which weapons have a DoT effect at compile time. In the next revision of your game, you might want to have a weapon that gains DoT at some point in time (e.g. when the user switches it to a different mode or when it has not been fired for 10 seconds). If you make that decision, you'll probably have to change all of your code to use Option 1 or Option 2.

A point about absolutism

I see other people who support option 3 saying things like "absolutely correct" and "end of story". You shouldn't believe them! Design patterns are not rules. There is more than one way to write good code. What really matters is that your code is easy to read, easy to write, easy to change in the future, is not unnecessarily dangerous, and does the intended job.

C++: using a base class as the implementation of an interface

If Base isn't derived from Interface, then you'll have to have forwarding calls in Derived. It's only "overhead" in the sense that you have to write extra code. I suspect the optimizer will make it as efficient as if your original idea had worked.

class Interface {
public:
virtual void myfunction() = 0;
};

class Base {
public:
virtual void myfunction() {/*...*/}
};

class Derived : public Interface, public Base {
public:
void myfunction() { Base::myfunction(); } // forwarding call
};

int main() {
Derived d;
d.myfunction();
return 0;
}

why it is not possible to implement inherited pure virtual method with 'using' directive?

using only brings a name into a scope.

It doesn't implement anything.

If you want Java-like get-implementation-by-inheritance, then you have to explicitly add the overhead associated with that, namely virtual inheritance, like this:

#include <iostream>

class Interface
{
public:
virtual void yell() = 0;
};

class Implementation
: public virtual Interface
{
public:
void yell()
{
std::cout << "hello world!" << std::endl;
}
};

class Test: private Implementation, public virtual Interface
{
public:
using Implementation::yell;
};

int main ()
{
Test t;
t.yell();
}


EDIT: a bit sneaky this feature, I had to edit to make the code compile with g++. Which didn't automatically recognize that the implementation yell and the interface yell were one and the same. I'm not completely sure what the standard says about that!

C++: Derived + Base class implement a single interface?

C++ doesn't notice the function inherited from Base already implements BaseFunction: The function has to be implemented explicitly in a class derived from Interface. Change it this way:

class Interface
{
public:
virtual void BaseFunction() = 0;
virtual void DerivedFunction() = 0;
};

class Base : public Interface
{
public:
virtual void BaseFunction(){}
};

class Derived : public Base
{
public:
virtual void DerivedFunction(){}
};

int main()
{
Derived derived;
}

If you want to be able to get away with only implementing one of them, split Interface up into two interfaces:

class DerivedInterface
{
public:
virtual void DerivedFunction() = 0;
};

class BaseInterface
{
public:
virtual void BaseFunction() = 0;
};

class Base : public BaseInterface
{
public:
virtual void BaseFunction(){}
};

class Derived : public DerivedInterface
{
public:
virtual void DerivedFunction(){}
};

class Both : public DerivedInterface, public Base {
public:
virtual void DerivedFunction(){}
};

int main()
{
Derived derived;
Base base;
Both both;
}

Note: main must return int

Note: it's good practise to keep virtual in front of member functions in the derived that were virtual in the base, even if it's not strictly required.

Why is a base class in C# allowed to implement an interface contract without inheriting from it?

The reason is that your comment is simply incorrect:

// Note that Derived does not provide implementation for IContract

Sure it does. Follow the logic through.

  • Derived is required to provide a public member corresponding to each member of IContract.
  • All inheritable members of a base class are also members of a derived class; that's the definition of inheritance.
  • Therefore Derived provides an implementation for IContract; its inherited member is a member that fulfills the requirement
  • Therefore, no error.

this feature is very-unintuitive and make code-inspection much harder. What do you think?

I think you shouldn't use the feature if you don't like it. If you find it confusing and weird to read code that uses this feature then encourage your coworkers who use this feature to stop doing so.

How is this feature different from any other feature where a method from a base class is used from a derived class? There are a number of different ways in which a method from a base class may be used or mentioned in a derived class -- method calls, overrides, method group conversions, and so on.

Furthermore, this is relatively speaking a simple, straightforward case. If you really want to complain about confusing interface semantics in C#, I'd spend my time complaining about interface reimplementation semantics. That's the one that really seems to bake people's noodles. I always have to look that thing up in the spec to make sure I'm getting the semantics right.

Does a derived class implement the interfaces of the base class in Typescript

When a class implements an interface, it doesn't actually affect the type of the class. The compiler checks that the class is compatible with the interface, but it doesn't use the interface as context to give types to the class's members. If you write class Foo implements Bar {...} and there is no compiler error, then it will be treated the same by the compiler as if you'd left out implements Bar and just wrote class Foo {...}. See microsoft/TypeScript#32082 and issues linked within for more information.

So BaseClass's controls property is inferred to have the empty object type {}. You might have expected BaseClass's controls property to be of type { [key: string] : number }, but that doesn't happen because such information would only come from the implements IBase clause, which is ignored when giving types to BaseClass's members. If you want to see an implementing class or a subclass have properties with particular types, you should annotate them:

class BaseClass implements IBase {
controls: { [key: string]: number } = {}; // annotated
}

class TestClass extends BaseClass {
// you might also want to annotate this, depending on intent
controls = { test: 'a' }; // error!
}

It's also important to note that, for better or worse, TypeScript's type system is not fully sound; there are some "holes" where TypeScript allows assignments that are inconsistent.

In a sound type system, subtyping should be transitive; that means, for any types A, B, and C, if A extends B and B extends C, then A extends C. And while this is quite often the case in TypeScript, it is sometimes violated. This is what's happening in your example: TestClass extends BaseClass, and BaseClass extends IBase, but TestClass does not extend IBase.

If we make a helper type function VerifyExtends<T, U> that only compiles if T extends U, we can witness this intransitivity of subtyping ourselves:

type VerifyExtends<T extends U, U> = void;    
type AB = VerifyExtends<TestClass, BaseClass> // okay
type BC = VerifyExtends<BaseClass, IBase> // okay
type AC = VerifyExtends<TestClass, IBase> // error!

TestClass extends BaseClass because the controls property in TestClass has an extra property that is not mentioned in that of BaseClass ({}), and you are allowed to extend object types by adding properties.

And BaseClass extends TestClass because its controls property {} has properties that conflict with the index signature (it has no properties at all)... and TypeScript will give implicit index signatures to such types.

But of course, TestClass does not extend IBase because extra properties and implicit index signatures are not mutually consistent. So we have weirdness.


In your case, I'd probably suggest explicit annotations, since that is your intent. But you will likely run into unsoundness sooner or later, and you should be prepared for it.

Playground link to code

Explicitly marking derived class as implementing interface of base class

It's explained in sections 13.4.4 to 13.4.6 of the C# 5 specification. The relevant sections are quoted below, but basically if you explicitly state that a class implements an interface, that triggers interface mapping again, so the compiler takes that class as the one to use to work out which implementation each interface member is mapped to.

13.4.4 Interface mapping

A class or struct must provide implementations of all members of the interfaces that are listed in the base class list of the class or struct. The process of locating implementations of interface members in an implementing class or struct is known as interface mapping.

Interface mapping for a class or struct C locates an implementation for each member of each interface specified in the base class list of C. The implementation of a particular interface member I.M, where I is the interface in which the member M is declared, is determined by examining each class or struct S, starting with C and repeating for each successive base class of C, until a match is located:

  • If S contains a declaration of an explicit interface member implementation that matches I and M, then this member is the implementation of I.M.
  • Otherwise, if S contains a declaration of a non-static public member that matches M, then this member is the implementation of I.M. If more than one member matches, it is unspecified which member is the implementation of I.M. This situation can only occur if S is a constructed type where the two members as declared in the generic type have different signatures, but the type arguments make their signatures identical.

...

13.4.5 Interface implementation inheritance

A class inherits all interface implementations provided by its base classes.
Without explicitly re-implementing an interface, a derived class cannot in any way alter the interface mappings it inherits from its base classes. For example, in the declarations

interface IControl
{
void Paint();
}
class Control: IControl
{
public void Paint() {...}
}
class TextBox: Control
{
new public void Paint() {...}
}

the Paint method in TextBox hides the Paint method in Control, but it does not alter the mapping of Control.Paint onto IControl.Paint, and calls to Paint through class instances and interface instances will have the following effects

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint(); // invokes Control.Paint();
t.Paint(); // invokes TextBox.Paint();
ic.Paint(); // invokes Control.Paint();
it.Paint(); // invokes Control.Paint();

...

13.4.6 Interface reimplementation

A class that inherits an interface implementation is permitted to re-implement the interface by including it in the base class list.

A re-implementation of an interface follows exactly the same interface mapping rules as an initial implementation of an interface. Thus, the inherited interface mapping has no effect whatsoever on the interface mapping established for the re-implementation of the interface. For example, in the declarations

interface IControl
{
void Paint();
}
class Control: IControl
{
void IControl.Paint() {...}
}
class MyControl: Control, IControl
{
public void Paint() {}
}

the fact that Control maps IControl.Paint onto Control.IControl.Paint doesn’t affect the re-implementation in MyControl, which maps IControl.Paint onto MyControl.Paint.

Trouble with Interfaces, Inheritance, and Polymorphism

You could make your base class implement the interface, then inherit your implementation classes from the base class, marking the base class and methods as abstract (MustInherit/MustOverride in VB parlance). This would give you your polymorphism and guarantee the interface.



Related Topics



Leave a reply



Submit