0

I'm currently reading about mixin classes and I think I unerstand everything more or less. The only thing I don't understand is why I don't need virtual functions anymore. (See here and here)

E.g. greatwolf writes in his answer here that virtual functions are not needed. Here is the example: (I just copied the essential parts)

struct Number
{
  typedef int value_type;
  int n;
  void set(int v) { n = v; }
  int get() const { return n; }
};

template <typename BASE, typename T = typename BASE::value_type>
struct Undoable : public BASE
{
  typedef T value_type;
  T before;
  void set(T v) { before = BASE::get(); BASE::set(v); }
  void undo() { BASE::set(before); }
};

typedef Undoable<Number> UndoableNumber;

int main()
{
  UndoableNumber mynum;
  mynum.set(42); mynum.set(84);
  cout << mynum.get() << '\n';  // 84
  mynum.undo();
  cout << mynum.get() << '\n';  // 42
}

But what happens now if I do something like this:

void foo(Number *n)
{
  n->set(84);    //Which function is called here?
}

int main()
{
  UndoableNumber mynum;
  mynum.set(42);
  foo(&mynum);
  mynum.undo();
  cout << mynum.get() << '\n';  // 42 ???
}

What value does mynum have and why? Does the polymorphism work in foo()?!?

Community
  • 1
  • 1
Thomas Sparber
  • 2,827
  • 2
  • 18
  • 34

3 Answers3

0

n->set(84); //Which function is called here?

Number::set will be called here.

Does the polymorphism work in foo()?!?

No, without virtual. If you try the code, you'll get an unspecified value because before doesn't be set at all.

LIVE

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
0

I compiled your code in VS 2013, and it gives an unspecified number.

You got no constructor in your struct, which means that the variable before is not initialized.

Noah Zuo
  • 98
  • 12
  • I think the reason is he has default initialized `UndoableNumber mynum;` could have been done like `UndoableNumber mynum{};` to zero initialize the member – RaGa__M Jul 29 '17 at 15:39
0

Your code example invokes undefined behaviour, because you try to read from the int variable n while it is not in a valid status. The question is not what value will be printed. Your program is not required to print anything, or do anything that makes sense, although you are likely using a machine on which the undefined behaviour will only present itself as a seeminly random value in n or on which it will mostly appear as 0.

Your compiler likely gives you an important hint if you allow it to detect such problems, for example:

34:21: warning: 'mynum.Number::n' is used uninitialized in this function [-Wuninitialized]

However, the undefined behaviour starts even before that. Here's how it happens, step by step:

UndoableNumber mynum;

This also creates the Number sub-object with an unintialised n. That n is of type int and can thus have its individual bits set to a so-called trap representation.

mynum.set(42);

This calls the derived-class set function. Inside of set, an attempt is made to set the before member variable to the uninitialised n value with the possible trap representation:

void set(T v) { before = BASE::get(); BASE::set(v); }

But you cannot safely do that. The before = BASE::get() part is already wrong, because Base::get() copies the int with the possible trap representation. This is already undefined behaviour.

Which means that from this point on, C++ as a programming language no longer defines what will happen. Reasoning about the rest of your program is moot.


Still, let's assume for a moment that the copy would be fine. What else would happen afterwards?

Base::set is called, setting n to a valid value. before remains in its previous invalid status.

Now foo is called:

void foo(Number *n)
{
  n->set(84);    //Which function is called here?
}

The base-class set is called because n is of type Number* and set is non-virtual.

set happily sets the n member variable to 84. The derived-class before remains invalid.

Now the undo function is called and does the following:

BASE::set(before);

After this assignment, n is no longer 84 but is set to the invalid before value.

And finally...

cout << mynum.get() << '\n';

get returns the invalid value. You try to print it. This will yield unspecified results even on a machine which does not have trap representation for ints (you are very likely using such a machine).


Conclusion:

  • C++ as a language does not define what your program does. It may print something, print nothing, crash or do whatever it feels like, all because you copy an unininitialised int.

  • In practice, crashing or doing whatever it feels like is unlikely on a typical end-user machine, but it's still undefined what will be printed.

  • If you want your derived-class set to be called when invoked on a Number*, then you must make set a virtual function in Number.

Community
  • 1
  • 1
Christian Hackl
  • 27,051
  • 3
  • 32
  • 62