Home > Software engineering >  How does binding work for layered inheritance in C ?
How does binding work for layered inheritance in C ?

Time:11-24

Say I have a virtual class:

class Tester {
  virtual void Validate();
}

class Test1 : Tester {
  void Validate(...) {
    /implementation
  }
}

class Test2 : Test1 {
  void Validate(...) {
    /implementation
  }
}

To my understanding, the virtual should make it dynamic binding. But I'm uncertain what would happen if we have layered classes (ie. Test2 inheriting Test1 inheriting Tester):

void RunTest(Tester test) {
  test.Validate(...);
}

...
Test2 test_candidate = Test2();
RunTest(test_candidate)

Which Validate method would this call?

CodePudding user response:

void RunTest(Tester test)

This parameter of the function is an object of type Tester. It isn't a Test1 nor is it a Test2, hence the call cannot be to anything other than Tester::Validate.

When you pass Test2 as an argument, it will be converted to Tester by copying the base sub object.

Virtual dispatch requires indirection. You must call the function through a reference or a pointer, because only a reference or a pointer can point to a base subobject of a derived object.

CodePudding user response:

Answer to your question

(see below or here why your example code does not behave like this)

Behaviour of "late" binding

Late binding always invokes the most specialized virtual function of the instance-type. In your case, all classes, Tester, Test1 and Test2 would invoke their own Validate(), as Test and Test2 override this function in each subclass.

However, if you would have

Test3 : public Test1 { /* Not overriding Validate() */ }

calling Validate() on an instance of Test3 would call Test1::Validate(), as that is the most specialized virtual implementation (it overrides Tester::Validate()).

Problem in your example code

In your example code, the function RunTest(Tester) passes the argument by value. If you pass an argument of type Test1, it makes a temporary copy of type Tester, as indicated by other answers, and if your compiler allows it at all without warning. This temporary copy is used in RunTest(Tester) and will always call Tester::Validate(), and that might not be the implementation of the object you passed as an argument to RunTest(Tester).

To use late binding, you should pass a reference or a pointer:

RunTest(Tester *t) { t->Validate(); }

or

RunTest(Tester &t) { t->Validate(); }

Using one of these will ensure that the behaviour is as described above (your answer).

CodePudding user response:

There are quite a few issues with the code you provided:

1. Private Inheritance Private Function Definitions

You need to declare your methods as public to be able to call them from an external function like RunTest. Also you probably want to inherit publicly in this case, so you can cast your Test2 class back to a Tester.

e.g.:

class Tester {
public:
  virtual void Validate();
};

class Test1 : public Tester {
public:
  void Validate();
};

class Test2 : public Test1 {
public:
  void Validate();
};

2. A few recommendations

If you want to override a virtual method in a child class, you can use override to mark your function as such. That way you'll get a compile-time error in case the function couldn't be overriden (e.g. because of mismatched arguments) (I'm assuming you meant void Validate() {} instead of void Validate(...))

Also if your classes contain any virtual methods, it's always a good idea to also provide a virtual destructor. (to properly clean up all the members in case it gets deleted by a pointer to its baseclass)

e.g.:

class Tester {
public:
  virtual void Validate() {
      // TODO
  }
  virtual ~Tester() = default;
};

class Test1 : public Tester {
public:
  void Validate() override {
      // TODO 
  }
};

class Test2 : public Test1 {
public:
  void Validate() override {
      // TODO
  }
};

3. The RunTest() function will slice the Test2 object

You're passing a Tester instance to RunTest() by value.
This will result in the object being sliced, i.e. you'll loose everything stored in the derived objects.

void RunTest(Tester test);

// this
Test2 t2;
RunTest(t2);

// is equivalent to:
Test2 t2;
Test t = t2;
RunTest(t);

so essentially you're calling the RunTest() method with just a Test object, not Test2.

you can fix this by either bassing by reference or by pointer:

void RunTest(Tester& test) {}
// or
void RunTest(Tester* test) {}

Working Example

#include <iostream>

class Tester {
public:
  virtual void Validate() {
      std::cout << "Tester Validate" << std::endl;
  }
  virtual ~Tester() = default;
};

class Test1 : public Tester {
public:
  void Validate() override {
      std::cout << "Test1 Validate" << std::endl;
      Tester::Validate();
  }
};

class Test2 : public Test1 {
public:
  void Validate() override {
      std::cout << "Test2 Validate" << std::endl;
      Test1::Validate();
  }
};

void RunTest(Tester& test) {
    test.Validate();
}

int main() {
    Test2 t;
    RunTest(t);
}

test it on godbolt!

  •  Tags:  
  • c
  • Related