【问题标题】:Static polymorphism with virtual functions具有虚函数的静态多态性
【发布时间】:2021-01-19 15:37:52
【问题描述】:

这是Static polymorphism with final or templates? 的后续问题。因此,仅静态多态性的最佳解决方案似乎是使用 CRTP。如果你想要运行时多态,虚函数是一个很好的解决方案。

我发现这不是很优雅,因为问题实际上非常相似(也许您想在某个时候更改行为)但代码实际上非常不同。如果解决方案非常相似并且仅在一个地方有所不同,那么我认为代码将更具表现力。

所以我想知道是否有一种方法可以通过虚函数获得仅静态的多态性。这可能类似于属性或某些结构,不允许指向抽象基类的指针。是否有这样的功能,如果没有,我是否遗漏了为什么不应该存在这样的功能?静态多态性和运行时多态性实际上是否比这样的特性所暗示的更加不同?

编辑:为了让问题和用例更清楚,这里有一些我想写的例子:

[[abstract]] class Base {
public:
  void bar() { /* do something using foo() */ }
private:
  virtual void foo() = 0;
};

class Derived1 : public Base {
public:
  Derived1();
private:
  void foo() override { /* do something */};
};

class Derived2 : pulic Base {
public:
  Derived2();
private:
  void foo() override { /* do something with data */ }
  int data;
};
  

其中不存在的属性 [[abstract]] 意味着不存在 Bass 类的实例,即使不存在指针也是如此。这将清楚地表达静态多态性,并且编译器可以优化掉虚拟调用,因为它们不存在。也不需要虚拟析构函数。

编辑 2:目标是提供一个抽象接口,该接口可以在进一步的派生类中稍作修改,并且具有与抽象类相同的扩展选项。所以主要实现还是在Base,虚函数的具体实现在Derived

【问题讨论】:

  • 由于虚函数仅用于运行时多态性,我不确定您尝试“使用虚函数获得仅静态多态性”是什么意思。这就像在干水中游泳一样,因为您不想被淋湿。
  • 什么代码有很大不同?对象上的静态或动态调度方法调用的位置?您绝对可以编写一个函数模板,根据类型参数生成虚拟和非虚拟调用。那是你想做的吗?这个问题很不清楚。
  • 我想要仅静态的多态,代码方面看起来与运行时多态的虚函数非常相似,例如,它可以很容易地交换以支持运行时多态。例如,我不喜欢 CRTP 包含大量样板,只是为了表达您想要静态多态而不是运行时多态。
  • @Henk 一个实际的用例会让你的问题更清楚。仅将其称为“静态多态性”在不同的上下文中可能意味着不同的东西。可以说,单独的 CRTP 不提供任何类型的多态性,因为没有通用的基础或接口契约,而是类似于将代码注入到任意数量的不同类中。
  • 对了,是不是你想要看起来相似的多态类型的声明和定义?还是使用它们的代码?

标签: c++ polymorphism


【解决方案1】:

你更有可能得到相反的东西,看起来像静态多态的运行时多态,或者看起来完全不同的东西。

针对 post-reflection C++(可能是 )的元类提案看起来足够强大,可以执行以下操作:

Interface IBob {
  void foo();
};

Implementation<Dispatch::Static> BobImpl:IBob {
  void foo() {}
};
Implementation<Dispatch::Dynamic> BobImpl:IBob {
  void foo() {}
};

大致按照您的要求做。 (元类提案中的语法远非最终的语法;然而,表达能力显然可以完成上述工作。

动态情况下会设置 vtable 等(但可能不是标准 C++ vtable),而在静态情况下 BobImpl 将与类型 bob 无关。

当然,到那时,我希望有这么多新的方式来表达 C++ 中的多态性,以至于“我希望我的 CRTP 写得像一个虚函数表 C++ 对象”有点像看到原子能技术来到地平线,并为它可以取代蒸汽火车上的燃煤器而感到兴奋。

【讨论】:

    【解决方案2】:

    看来,仅静态多态性的最佳解决方案是使用 CRTP。如果你想要运行时多态,虚函数是一个很好的解决方案。

    在您的链接问题中,您使用 CRTP 来强制执行接口:

    template <typename TData>
    struct Base {
       void foo() {
           static_cast<TData*>(this)->doFoo();
       }
    

    使用静态多态,但它是一个非常特殊的用途。如果您的派生类没有合适的doFoo 方法,它什么也不做,只会拒绝编译。所以我不知道你是怎么得出结论的。

    我发现这不是很优雅,因为问题实际上非常相似(也许您想在某个时候更改行为)但代码实际上非常不同。如果解决方案非常相似并且仅在一个地方有所不同,那么我认为代码会更具表现力。

    你失去了我。运行时多态性影响 C++ 中的两件事:

    1. 声明,因为您必须有一个基类、virtual 关键字,以及可选的overridefinal

    2. 使用,当调用站点使用虚拟调度找到正确的方法实现时

      (尽管如上所述,当静态类型已知时,这可能会被优化)。

    还要注意,虽然多态性经常被讨论为对象之间的关系,但在 C++ 中我们实际上只讨论方法分派。

    静态多态性影响调用站点。您的其他问题使用 CRTP 这一事实并不意味着这是使用静态多态性的唯一方法。

    如果我写一个模板函数

    template <typename T>
    void foo_it(T&& t) { t.foo(); }
    

    然后使用静态多态性。它适用于任何具有合适的foo 方法的T,无论它是否源自Base&lt;T&gt;。它甚至适用于覆盖其他基类的虚拟foo()T。这是duck typing

    由于不清楚你希望你的[[abstract]] Base达到什么目的,我只能建议你写一下

    class Derived {
    public:
      void foo();
    };
    

    并将其传递给期望某种类型实现 foo() 的函数模板。


    作为您编辑的后续行动

    所以主要的实现还是在Base里面,虚函数的具体实现在Derived里面

    使用 CRTP 完全可以做到这一点。这是一个使用静态多态性的实现细节,不是类似虚拟的层次结构。

    例如

    template <typename Derived>
    struct Template {
      Derived* virt() { return static_cast<Derived*>(this); }
    
      int foo(int i) {
        return i + virt->detail(i) + virt->extra();
      }
    };
    
    struct A: public Template<A> {
      int detail(int i) { return i*i; }
      int extra() { return 17; }
    };
    
    struct B: public Template<B> {
      int detail(int i) { return i % 23; }
      int extra() { return -42; }
    };
    

    创建两个独立的类型AB,它们提供相同的接口int foo(int),并且恰好共享一些代码作为实现细节。

    它不会创建层次结构。如果你编写一个函数模板,它接受一些T 类型的对象并在其上调用int T::foo(int) 方法,这将起作用。那就是静态多态性。它不需要共享基类。

    【讨论】:

    • 我试图更清楚地说明我想要实现的目标。 Base 类应该提供一个可以静态使用的接口。
    • 我的意思是你不需要一个基类接口来静态使用任何东西。您可以使用 CRTP 来构建您的一般实现,但这是一个细节。 CRTP&lt;Impl1&gt;CRTP&lt;Impl2&gt; 不需要共同的基类即可在静态多态中使用。它们都提供相同的鸭子类型,这就足够了。
    • 是的,抱歉,我想我的目标不是很明确。我基本上想要具有与经典抽象类的实现相同的特征的静态多态性(并且不像 CRTP 完全不同)。我希望我现在对问题的编辑可以使这一点更清楚。
    • 当我们讨论运行时多态性时,这意味着我们可以拥有一个真正派生类型对象的基类对象引用。使用它专门在派生类型中由虚拟钩子定制的基础中实现是一种边缘情况。它不是多态性的主要或一般用途。您不能只说“多态性”并期望人们立即想到这一点。
    【解决方案3】:

    假设问题是如何在编译时强制派生类

    • 实现基础“抽象接口
    • 没有多态性
    • 保持实现非公开,但基类成员可以访问

    以下可能是一种方法。

    #include <type_traits>
    #include <iostream>
    using std::cout;
    using std::endl;
    
    template<class Impl> class Base {
    protected:
      void foo() {
        Bridge::virtual_foo(static_cast<Impl&>(*this));
      }
    
      struct Bridge : public Impl {
        static void virtual_foo(Impl &that) {
          static constexpr void (Impl::*fn)() = &Bridge::foo;
          (that.*fn)();
        }
        static_assert(std::is_same<void (Impl::*)(), decltype(&Bridge::foo)>::value, "foo not implemented");
      };
    
    public:
      void bar() {
        cout << "begin Base::bar" << endl;
        foo();
        cout << "end Base::bar" << endl << endl;
      }
    };
    
    class Good : public Base<Good> {
    protected:
      void foo() {
        cout << "in Good::foo" << endl;
      }
    };
    
    class Bad : public Base<Bad> {
    };
    
    int main() {
      Good().bar();
    // Bad().bar(); // static_assert: 'foo' not implemented
    }
    

    Output:

    begin Base::bar
    in Good::foo
    end Base::bar
    

    【讨论】:

      猜你喜欢
      • 2018-07-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多