[英]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
根本不会进行设置。
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
. 这还会创建带有未初始化的
n
的Number
子对象。 That n
is of type int
and can thus have its individual bits set to a so-called trap representation . n
是int
类型的,因此可以将其各个位设置为所谓的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
是因为n
是Number*
类型,而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.