【问题标题】:Does static polymorphism make sense for implementing an interface?静态多态对实现接口有意义吗?
【发布时间】:2013-12-25 09:25:34
【问题描述】:

祝大家圣诞快乐!

我正在学习静态多态性,并且正在阅读 Andrei Alexandrescu 关于基于策略的设计的优秀书籍。我在我的代码中遇到了以下内容:我有接口Interface,它指定方法Foo 必须存在。该接口将由类Impl 实现。我有以下两种选择:

1) 动态多态性

class Interface {
public:
    virtual void Foo() = 0;
}

class Impl : public Interface {
public:
    void Foo() {};
}

2) 静态多态

class Impl {
{
public:
    void Foo() {};
}

template <class I>
class Interface : public I
{
public:
    void Foo() { I::Foo(); } //not actually needed
}

在这种情况下使用静态多态有意义吗?与第一种方法相比,第二种方法有什么好处吗?接口只指定了一些方法的存在,而且它的机制对于不同的实现是相同的——所以和书中描述的情况不太一样,所以我觉得我可能只是把事情复杂化了。

更新:我在运行时不需要多态行为;正确的实现在编译时就知道了。

【问题讨论】:

  • 与第二种方法相比,第二种方法绝对没有任何好处。 ;-)
  • 查看 Wiki 中的优点和缺点:en.wikipedia.org/wiki/…
  • 您想了解如何从 Java 接口转换到 C++ 吗?
  • 不,我不懂 Java。
  • 您可以在第一个示例中使用Interface::Foo();,因此对基类的调用没有区别。但是当您有多个实现 Foo 函数的基类时,第二种解决方案有一个好处。如果是这样,当使用子类时,可以通过这样做来选择正确的基类:Interf&lt;Impl&gt;

标签: c++ templates polymorphism static-polymorphism policy-based-design


【解决方案1】:

检查接口。

动态多态确实会强制孩子尊重接口。

静态多态性不会强制孩子尊重接口 (直到你真正调用该函数),所以,如果你不提供有用的方法, 你可以直接使用Impl

class InvalidImpl {}; // Doesn't respect interface.
void bar()
{
    InvalidImpl invalid;

    // this compiles, as not "expected" since InvalidImpl doesn't respect Interface.
    CRTP_Interface<InvalidImpl> crtp_invalid; 

#if 0 // Any lines of following compile as expected.
    invalid.Foo();
    crtp_invalid.Foo();
#endif
}

您有第三种方法使用特征来检查一个类是否验证了一个接口:

#include <cstdint>
#include <type_traits>

// Helper macro to create traits class to know if class has a member method
#define HAS_MEM_FUNC(name, Prototype, func)                             \
    template<typename U>                                                \
    struct name {                                                       \
        typedef std::uint8_t yes;                                       \
        typedef std::uint16_t no;                                       \
        template <typename T, T> struct type_check;                     \
        template <typename T = U>                                       \
        static yes &chk(type_check<Prototype, &T::func> *);             \
        template <typename > static no &chk(...);                       \
        static constexpr bool value = sizeof(chk<U>(0)) == sizeof(yes); \
    }

// Create traits has_Foo.
HAS_MEM_FUNC(has_Foo, void (T::*)(), Foo);

// Aggregate all requirements for Interface
template <typename T>
struct check_Interface :
    std::integral_constant<bool, has_Foo<T>::value /* && has_otherMethod<T>::value */>
{};

// Helper macros to assert if class does respect interface or not.
#define CHECK_INTERFACE(T) static_assert(check_Interface<T>::value, #T " doesn't respect the interface")
#define CHECK_NOT_INTERFACE(T) static_assert(!check_Interface<T>::value, #T " does respect the interface")

使用 C++20 概念,可以以不同的方式编写特征:

// Aggregate all requirements for Interface
template <typename T>
concept InterfaceConcept = requires(T t)
{
    t.foo();
    // ...
};

#define CHECK_INTERFACE(T) static_assert(InterfaceConcept<T>, #T " doesn't respect the interface")

让我们测试一下:

class Interface {
public:
    virtual void Foo() = 0;
};

class Child_Impl final : public Interface {
public:
    void Foo() override {};
};

#if 0 // Following doesn't compile as expected.
class Child_InvalidImpl final : public Interface {};
#endif

template <class I>
class CRTP_Interface : public I
{
public:
    void Foo() { I::Foo(); } // not actually needed
};

class Impl { public: void Foo(); }; // Do respect interface.
class InvalidImpl {};               // Doesn't respect interface.

CHECK_INTERFACE(Interface);
CHECK_INTERFACE(Child_Impl);
CHECK_INTERFACE(Impl);
CHECK_INTERFACE(CRTP_Interface<Impl>);

CHECK_NOT_INTERFACE(InvalidImpl);
CHECK_INTERFACE(CRTP_Interface<InvalidImpl>); // CRTP_Interface<T> _HAS_ Foo (which cannot be invoked)

性能

使用动态多态,您可以为虚拟调用付费。您可以通过将final 添加为class Child final : public Interface 来减少一些虚拟呼叫。

所以编译器可能会优化如下代码:

void bar(Child& child) { child.Foo(); } // may call Child::Foo not virtually.

但它不能做任何魔术(假设 bar 没有内联):

void bar(Interface& child) { child.Foo(); } // have to virtual call Foo.

现在,假设您的界面中有:

void Interface::Bar() { /* some code */ Foo(); }

我们处于第二种情况,我们必须虚拟呼叫Foo

静态多态通过以下方式解决了这个问题:

template<class Derived>
void Interface<Derived>::Bar() { /* some code */ static_cast<Derived*>(this)->Foo(); }

【讨论】:

  • 使用using Derived::func检查接口怎么样?
【解决方案2】:

使用静态多态是否有意义取决于你如何使用类。

虚函数引入了一定程度的间接性。虚函数允许使用指向基类对象的指针或引用调用派生类中的方法(这对所有派生类都是通用的)。

静态多态不使用公共基类。每个派生类都使用它自己的基类。这些基类通常是从通用类模板创建的。然而,它们是不同的类别。这导致了这样的事情,例如对此类对象的指针或引用不能存储在公共容器中。

【讨论】:

  • 在我的示例中,接口类是派生类(对于静态示例)。在这两种情况下,我都可以拨打Interface.Foo()
  • @DanNestor 否。在您的示例中,您将拥有 Interface&lt;X&gt;.Foo()Interface&lt;Y&gt;.Foo()Interface&lt;X&gt;Interface&lt;Y&gt; 是 2 个不同的类。您不能将 Interface&lt;Y&gt; 存储在 std::vector&lt;Interface&lt;X&gt;&gt; 中,反之亦然。
  • 哦,我明白了,谢谢。但是,我在运行时不需要多态行为,我会更新问题。
  • 关于“指针或对此类对象的引用不能存储在公共容器中”的观点非常好。以前没有从这个角度考虑过。
猜你喜欢
  • 1970-01-01
  • 2012-09-08
  • 2014-08-06
  • 1970-01-01
  • 2013-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多