【问题标题】:Polymorphism in mixin classes - virtual functionsmixin 类中的多态性 - 虚函数
【发布时间】:2015-10-31 09:48:39
【问题描述】:

我目前正在阅读有关 mixin 类的信息,我想我或多或少地理解了所有内容。我唯一不明白的是为什么我不再需要虚函数了。 (见herehere

例如Greatwolf 在his answer 中写道,不需要虚函数。这是示例:(我只是复制了基本部分)

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
}

但是如果我做这样的事情现在会发生什么:

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 ???
}

mynum 有什么价值,为什么?多态性在foo()?! 中是否有效?

【问题讨论】:

  • 你在那个答案的cmets中有答案。
  • mixin 旨在为派生类添加功能。它不打算用作接口(需要虚拟功能)。

标签: c++ polymorphism mixins virtual-functions


【解决方案1】:

n->set(84); //这里调用了哪个函数?

Number::set 将在此处调用。

多态在 foo() 中有效吗?!?

不,没有virtual。如果您尝试该代码,您将得到一个未指定的值,因为根本没有设置 before

LIVE

【讨论】:

    【解决方案2】:

    我在 VS 2013 中编译了您的代码,它给出了一个未指定的数字。

    你的结构中没有构造函数,这意味着之前的变量没有初始化。

    【讨论】:

    • 我认为原因是他已经默认初始化UndoableNumber mynum; 可以像UndoableNumber mynum{}; 一样将成员初始化为零
    【解决方案3】:

    您的代码示例调用了未定义的行为,因为您尝试在int 变量n 处于无效状态时进行读取。问题不在于将打印什么值。您的程序不需要打印任何东西,或做任何有意义的事情,尽管您可能使用的机器上未定义的行为只会在n 中将自己呈现为看似随机的值,或者它主要显示为 0 .

    如果您允许编译器检测此类问题,它可能会给您一个重要提示,例如:

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

    但是,未定义的行为甚至在此之前就开始了。下面是它是如何发生的,一步一步来:

    UndoableNumber mynum;
    

    这还会创建带有未初始化的nNumber 子对象。 nint 类型,因此可以将其各个位设置为所谓的 trap representation

    mynum.set(42);
    

    这会调用派生类set 函数。在set 内部,尝试将before 成员变量设置为未初始化的n 值,并带有可能的陷阱表示:

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

    但你不能安全地做到这一点。 before = BASE::get() 部分已经错了,因为Base::get() 复制了int 与可能的陷阱表示。 这已经是未定义的行为。

    这意味着从现在开始,C++ 作为一种编程语言不再定义会发生什么。对程序的其余部分进行推理是没有实际意义的。


    不过,让我们暂时假设副本会很好。之后还会发生什么?

    Base::set 被调用,将n 设置为有效值。 before 保持之前的无效状态。

    现在foo 被调用:

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

    基础set被调用是因为n的类型是Number*,而set非虚拟的

    set 愉快地将n 成员变量设置为84。派生类before 仍然无效

    现在调用undo 函数并执行以下操作:

    BASE::set(before);
    

    在此分配之后,n 不再是 84,而是设置为无效的 before 值。

    最后……

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

    get 返回无效值。您尝试打印它。即使在没有ints 的陷阱表示的机器上,这也会产生未指定的结果(您很可能使用这样的机器)。


    结论:

    • C++ 作为一种语言并没有定义你的程序做什么。它可能会打印一些东西,什么也不打印,崩溃或做任何感觉,这一切都是因为你复制了一个未初始化的int

    • 实际上,在典型的最终用户计算机上,崩溃或做任何感觉不太可能,但仍不确定将打印什么。

    • 如果您希望在Number* 上调用派生类set,则必须在Number 中将set 设为virtual 函数。

      李>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-07-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-04
      • 1970-01-01
      • 2013-02-11
      • 2019-11-17
      相关资源
      最近更新 更多