0

I am trying to understand virtual functions as well as virtual inheritance. For the most part, I think I successfully grasp it and its relation to polymorphism, I have been reading how the vptr works with derived objects and what not, however the example below is throwing me off, it is one I found in an Algorithms and Data Structures in C++ book:

#include <iostream>
using namespace std;

class Class1 {
public:
    virtual void f() {
        cout << "Function f() in Class1\n";
    }

    void g() {
        cout << "Function g() in Class1\n";
    }
};

class Class2 {
public:
    virtual void f() {
        cout << "Function f() in Class2\n";
    }

    void g() {
        cout << "Function g() in Class2\n";
    }
};

class Class3 {
public:
    virtual void h() {
        cout << "Function h() in Class3\n";
    }
};

int main() {
    Class1 object1, *p;
    Class2 object2;
    Class3 object3;

    p = &object1;
    p->f();
    p->g();

    p = (Class1*) &object2;
    p->f();
    p->g();

    p = (Class1*) &object3;
    p->f(); // possibly abnormal program termination;
    p->g();
    // p->h(); // h() is not a member of Class1;
    return 0;
}

Output:

Function f() in Class1
Function g() in Class1
Function f() in Class2
Function g() in Class1
Function h() in Class3
Function g() in Class1

I understand everything except the last p->f();. As a preface, I get that we cannot directly call h() from p because of the casting into a Class1 type, but shouldn't the Class3 vptr point only to the virtual function h() in its vtable, and if so, then shouldn't it look for f() in Class3's vtable and not find it? Why does it think that Class1::f() is Class3::h(), its not like Class3 is inherited from Class1...and for the record, if we rewrite Class3 to be:

class Class3 : public Class1 { // publicly inherit from Class1 is only difference
    public:
    virtual void h() {
        cout << "Function h() in Class3\n";
    }
};

and upcast into a Class1 pointer, and then call p->f() it gives us Class1::f() as expected, I just cannot figure out why it even lets us call p->f() when Class3 does not inherit from Class1.

Dominic Farolino
  • 1,362
  • 1
  • 20
  • 40
  • 3
    There is no virtual inheritance in your code! – Kerrek SB May 27 '15 at 16:31
  • `I understand everything except the line p->f();` except there are 3 lines `p->f();` – UmNyobe May 27 '15 at 16:32
  • `p = (Class1*) &object2;` Is essentially the same as `p = reinterpret_cast &object2;`. So what else do you expect? – πάντα ῥεῖ May 27 '15 at 16:32
  • @UmNyobe sorry, specified it – Dominic Farolino May 27 '15 at 16:33
  • 2
    possible duplicate of [When should static\_cast, dynamic\_cast, const\_cast and reinterpret\_cast be used?](http://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-used) – UmNyobe May 27 '15 at 16:35
  • 1
    where is the parent child relationship statements like `class Class2:public Class1` ? – Fredrick Gauss May 27 '15 at 16:36
  • You need to understand that C style cast of address means ***I am a grown man, I know better than anyone, even you compiler. It is going to be fine.*** – UmNyobe May 27 '15 at 16:36
  • @DomFarolino Remove all of those casts. Recompile the code. What error(s) do you get? If you do get them, understand the errors. All you're doing is overriding the legitimate compiler errors by using C-style casts. So now you have a nutso program, so don't be shocked that behaves weird. – PaulMcKenzie May 27 '15 at 16:38
  • Which book and page please – Lightness Races in Orbit May 27 '15 at 16:44
  • @PaulMcKenzie @UmNyobe ok so the C style cast is forcing the conversion I assume, because when I just try and do something like this: `Class3 *c3 = new Class3(); Class1 *c1 = c3;` typical upcasting, it does not work, I assume because the types are totally unrelated and have no inheritance between them, and when I use C-style casting, it just forces it no matter what, and then continues doing the actions explained in Mark Ransom's answer below..is this correct? – Dominic Farolino May 27 '15 at 16:48
  • @LightnessRacesinOrbit Data Structures and Algorithms in C++ by Adam Drozdek p.22 I copied the code from it – Dominic Farolino May 27 '15 at 16:49
  • @DomFarolino: Looks like a (glaring) error in the book. It's worth noting that's it been an error since _at least_ [the 2nd edition in 1995](http://iit.qau.edu.pk/books/Data%20Structure%20And%20Algorithms%20In%20C++%202nd%20ed%20-%20Adam%20Drozdek.pdf), and is still there in [the 4th edition from 2012](http://www.amazon.co.uk/Data-Structures-Algorithms-Adam-Drozdek/dp/1133608426)!!! Oh dear. – Lightness Races in Orbit May 27 '15 at 17:04
  • @LightnessRacesInOrbit I don't want to totally discount the book because of that but I would like to know what you would recommend as far as a good DSA book goes – Dominic Farolino May 27 '15 at 17:13
  • @Dom: Well there's [this list](http://stackoverflow.com/q/388242/560648); it is not DSA-focused, but that may not be a bad thing. – Lightness Races in Orbit May 27 '15 at 17:17
  • @LightnessRacesinOrbit maybe the book is trying to say something about object layout with that example. Even though it invokes undefined behavior, the output is probably consistent enough across platforms to be useful in making a point. Unfortunately I don't have the book so I don't know the context around the code. – Mark Ransom May 27 '15 at 23:05
  • @MarkRansom: Given that it's a chapter trying to teach polymorphism, my view is that the intention is obviously inheritance. – Lightness Races in Orbit May 28 '15 at 05:22

3 Answers3

5

This example is bad, you are casting unrelated types to each other. This results in undefined behavior.

The code is taking advantage of the supposed similarities in layout between the different object types. The vtable will be in the same location in each object, and the virtual functions will be found by index, not name. So a call to the first virtual function of Class1 generates a call through the table, resulting in a call to the first virtual function of Class3. Remember though that this is an accident and is not guaranteed by any properties of C++.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • Thank you, it was apparent to me that it was calling the first virtual function of Class3 no matter what it was called and that confused the hell out of me. Lastly, is it the cast type that is partly at fault for this, because if I try and declare `Class3 *c3 = new Class3(); Class1 *c1 = c3;` it does not let me do this, is this because there is no inheritance (i.e. not related types?) – Dominic Farolino May 27 '15 at 16:46
  • @DomFarolino exactly, it won't let you do the assignment without casting because they're unrelated types. The explicit casts overrides the compiler's objections but opens you up to undefined behavior. – Mark Ransom May 27 '15 at 16:50
5

I just cannot figure out why it even lets us call p->f() when Class3 does not inherit from Class1.

This is really what you're asking. The question has nothing to do with virtual inheritance, or virtual functions. Everything in your question apart from the above text is completely irrelevant.

This question is really why are you not prevented from doing the following at compile-time:

struct Foo
{
   void foo() {}
};

struct Bar
{
   void bar() {}
};

int main()
{
   Foo f;
   ((Bar*)&f)->bar();
}

The answer is that this is what C-style casts do: they allow you to override the type system. It's up to you to get that right and not to lie to the compiler, as you're doing at the moment. By doing so, your program has undefined behaviour and that's that.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • I understand, I did not fully understand the difference in cast types before posting this question. I just figured what the book showed me was similar to regular upcasting like `Right* right = bottom;` found here(http://www.phpcompiler.org/articles/virtualinheritance.html) however clearly it is a much more dangerous form of casting, thank you – Dominic Farolino May 27 '15 at 16:51
2

You or the author of the book probably just forgot the parent/child inheritance relationship to make this sample code worth something else than undefined behaviour :)

#include <iostream>
using namespace std;

class Class1 {
public:
  virtual void f() {
    cout << "Function f() in Class1\n";
  }

  void g() {
    cout << "Function g() in Class1\n";
  }
};

class Class2 : public Class1 {
public:
  virtual void f() {
    cout << "Function f() in Class2\n";
  }

  void g() {
    cout << "Function g() in Class2\n";
  }
};

class Class3 : public Class1 {
public:
  virtual void h() {
    cout << "Function h() in Class3\n";
  }
};

int main() {
  Class1 object1, *p;
  Class2 object2;
  Class3 object3;

  p = &object1;
  p->f();
  p->g();

  p = (Class1*) &object2;
  p->f();
  p->g();

  p = (Class1*) &object3;
  p->f();
  p->g();
  // p->h(); // h() is not a member of Class1;
  return 0;
}

Which prints:

Function f() in Class1
Function g() in Class1
Function f() in Class2
Function g() in Class1
Function f() in Class1
Function g() in Class1

Which is a decent basic polymorphism example.

Drax
  • 12,682
  • 7
  • 45
  • 85
  • ok that makes sense, this is what it seemed to me that the example should be, basic polymorphism and inheritance however me unknowingly using C-style casts for a horrible purpose and being perplexed at the _funny_ behavior I was getting is what happened when I read this example which I see as fairly poor now – Dominic Farolino May 27 '15 at 16:54