简体   繁体   English

Mixin类中的多态性-虚拟函数

[英]Polymorphism in mixin classes - virtual functions

I'm currently reading about mixin classes and I think I unerstand everything more or less. 我目前正在阅读有关mixin类的信息,我认为我或多或少都无法理解所有内容。 The only thing I don't understand is why I don't need virtual functions anymore. 我唯一不了解的是为什么我不再需要虚函数了。 (See here and here ) (请参见此处此处

Eg greatwolf writes in his answer here that virtual functions are not needed. 例如,greatwolf在他的回答中写道,不需要虚函数。 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? mynum具有什么价值,为什么? Does the polymorphism work in foo() ?!? 多态性在foo()起作用吗?

n->set(84); n->设置(84); //Which function is called here? //在这里调用哪个函数?

Number::set will be called here. Number::set将在此处调用。

Does the polymorphism work in foo()?!? 多态性在foo()中起作用吗?!

No, without virtual . 不,没有virtual If you try the code, you'll get an unspecified value because before doesn't be set at all. 如果你尝试的代码,你会得到一个未确定的值,因为before根本不会进行设置。

LIVE 生活

I compiled your code in VS 2013, and it gives an unspecified number. 我在VS 2013中编译了您的代码,但给出了一个未指定的数字。

You got no constructor in your struct, which means that the variable before is not initialized. 您的结构中没有构造函数,这意味着before变量没有初始化。

Your code example invokes undefined behaviour , because you try to read from the int variable n while it is not in a valid status. 您的代码示例调用了undefined behavior ,因为您尝试从int变量n处于无效状态时对其进行读取。 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. 尽管您可能使用的机器上未定义的行为只会以n的明显随机值出现或在大多数情况下以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 . 这还会创建带有未初始化的nNumber子对象。 That n is of type int and can thus have its individual bits set to a so-called trap representation . nint类型的,因此可以将其各个位设置为所谓的trap表示

 mynum.set(42); 

This calls the derived-class set function. 这将调用派生类set函数。 Inside of set , an attempt is made to set the before member variable to the uninitialised n value with the possible trap representation: set内部,尝试使用可能的陷阱表示将before成员变量设置为未初始化的n值:

 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. before = BASE::get()部分已经是错误的,因为Base::get()复制带有可能的陷阱表示形式的int This is already undefined behaviour. 这已经是不确定的行为。

Which means that from this point on, C++ as a programming language no longer defines what will happen. 这意味着从现在开始,C ++作为一种编程语言不再定义将要发生的事情。 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. 调用Base::set ,将n设置为有效值。 before remains in its previous invalid status. before保持其先前无效的状态。

Now foo is called: 现在foo被称为:

 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是因为nNumber*类型,而set 是非虚拟的

set happily sets the n member variable to 84. The derived-class before remains invalid . set愉快地设置n成员变量84.派生类before 保持无效

Now the undo function is called and does the following: 现在, undo函数被调用并执行以下操作:

 BASE::set(before); 

After this assignment, n is no longer 84 but is set to the invalid before value. 在分配之后, n不再是84,而是被设置为无效的before值。

And finally... 最后...

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

get returns the invalid value. get返回无效值。 You try to print it. 您尝试打印它。 This will yield unspecified results even on a machine which does not have trap representation for int s (you are very likely using such a machine). 即使在没有用于int的陷阱表示的机器上,这也会产生不确定的结果(很可能使用这种机器)。


Conclusion: 结论:

  • C++ as a language does not define what your program does. C ++作为一种语言并不能定义程序的功能。 It may print something, print nothing, crash or do whatever it feels like, all because you copy an unininitialised int . 它可能会打印某些内容,什么都不打印,崩溃或执行任何感觉,这都是因为您复制了未初始化的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 . 如果希望在Number*上调用派生类set时被调用,则必须在Number set virtual函数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM