【问题标题】:Class in c++ is still abstract even if the template class implements the abstract methods即使模板类实现了抽象方法,C++ 中的类仍然是抽象的
【发布时间】:2015-10-08 10:25:13
【问题描述】:

如果编译器认为模板类的实例仍然是抽象的,即使要派生的类实现了这个抽象方法,我想知道这是否符合标准。

我的编译器是 Microsoft C/C++ Compiler 18.00.40629 (Visual Studio 2013)

下面的课程:

class MyClass {
    virtual void Method() = 0;
};

template <typename T>
class MyClassT : public MyClass, public T {
};

class MyTemplateData {
    void Method() {
    }
};

MyClassT<MyTemplateData> MyInstance;

据我了解,MyClassT&lt;MyTemplateData&gt; 类是一个有效的实例,它实现了所有抽象方法,因为 MyClassT 派生自 MyClassMyTemplateData。为了能够通过保持所需行为来编译此类,我应该进行哪些更改?

有一个解决方案,但让我解释一下为什么我不想关注:

template <typename T>
class MyClassT : public MyClass, public T {
    void Method() {
        T::Execute();
    }
};

在上述情况下,它确实会编译,但是这会假设我的T 类总是实现Method。但是我想要的是在MyClassT 中有一个空的Method,而不是如果T 类实现了它,则应该被T::Method 覆盖。

所以下面的代码也可以编译,但是T::Method 永远不会被调用。

template <typename T>
class MyClassT : public MyClass, public T {
    void Method() {
    }
};

MyClass* Interface = new MyClassT<MyTemplateData>();
Interface->Method(); // <- here, MyTemplateData::Method() gets never called

是否有解决方案,或者这是编译器特定的错误?

编辑:再举一个例子,这次编译器应该确切地知道派生类实现了所需的抽象方法。

struct InterfaceA {
    virtual void MethodA() = 0;
};

struct InterfaceAB : InterfaceA {
    virtual void MethodB() = 0;
};

class ImplA : public virtual InterfaceA {
public:
    void MethodA() {
    }
};

class ImplAB : public virtual InterfaceAB, public ImplA {
    void MethodB() {
    }
};

编译器期望类ImplAB 实现MethodA,因为InterfaceAB 需要同时实现MethodAMethodB。然而,由于ImplAB 也是从ImplA 派生的,并且编译器确切地知道ImplA 实现了该接口,所以应该没有问题。

至少应该有一个关键字来提示编译器在哪里实现抽象方法,如下例所示。我看不出有任何理由至少在以任何方式使用 VTABLE 时应该避免这种情况。

class ImplABCD : public implements InterfaceABCD, public ImplA, public ImplB, public ImplC, public ImplC {
};

implements 关键字表示来自InterfaceABCD 的所有抽象方法都将在ImplABCD 中实现或已经在其中一个继承类中实现。如果存在签名歧义,则在从具有相同基类的多个类继承时,按照 c++ 规范已经完成的处理情况。

【问题讨论】:

  • clang/gcc 也不编译这段代码。
  • 将 CRTP 保留在图片中会使您的理解变得不必要地复杂化。只需考虑等效的 class X : public MyClass, public MyTemplateData { }; - 希望您已经知道并接受这不是提供虚函数实现的有效方式,但如果不让我们知道,我们将拖入标准引号。
  • 更一般地说,听起来您可以通过使用 SFINAE 检查 T::Execute 是否存在来完全按照您的要求进行操作 - 只需 google has_member_functionhas_member 或类似名称,您应该会看到如何。
  • @TonyD 据我了解,多个继承的类可以实现/覆盖彼此的方法,我错了吗?
  • @bkausbk - 是的,你错了(对于 C++)。如果从抽象类继承,则必须实现纯虚函数。即使其他基类也实现了它。

标签: c++ templates inheritance overriding virtual-functions


【解决方案1】:

如果您不想使用@ForEveR 解决方案,那么您可以执行以下操作。

使用 SFINAE std::enable_if。 类似于:

template <typename T>
struct MyClassT : public MyClass, public T
{
    typename std::enable_if<has_func_method<T>::value, void>::type Method()
    {
        T::Method();
    }

    typename std::enable_if<!has_func_method<T>::value, void>::type Method()
    {
        //do nothing
    }
};

编辑: 您可以在类本身而不是在内部使用 SFINAE。 那样可能会更好。 这是一个工作示例:

// Example program
#include <iostream>
#include <string>

struct MyClass
{
    virtual void Method() = 0;
};

template< typename C, typename = void >
struct has_func_method
  : std::false_type
{};

template< typename C>
struct has_func_method< C, typename std::enable_if<
    std::is_same<
        decltype( std::declval<C>().Method()),
        void
    >::value
>::type >
  : std::true_type
{};

template <typename T, bool has_method = has_func_method<T>::value>
struct MyClassT : public MyClass, public T
{
};

template <typename T>
struct MyClassT<T, false> : public MyClass, public T
{
    void Method()
    {
        //do nothing
        std::cout << "doing nothing" << std::endl;
    }
};

template <typename T>
struct MyClassT<T, true> : public MyClass, public T
{
    void Method()
    {
        std::cout << "calling T::Method" << std::endl;
        T::Method();
    }
};

struct DataWithoutMethod
{
};

struct DataWithMethod
{
    void Method()
    {
        std::cout << "method called" << std::endl;
    }
};


int main()
{
    MyClassT<DataWithoutMethod> data;
    data.Method();
    MyClassT<DataWithMethod> data2;
    data2.Method();
}

【讨论】:

  • 不错; override 会更好;-)
  • 这个解决方案确实看起来很有趣,但是 SFINAE 的使用是不是有点“声名狼藉”(不干净的设计)?
  • @bkaus:这是一个大规模的 hack,但是,大多数 C++ 模板元编程也是如此。
  • 请注意,void 模板参数 std::enable_if 是多余的,因为无论如何这是默认值。也许您想明确返回类型,这是一个不错的风格选择。
  • @bkausbk 不幸的是,在我们的项目和编译器开始使用 Concepts 之前,我们一直坚持使用我们的技巧和变通方法。
【解决方案2】:

我想知道这是否符合标准的行为

是的。

如果编译器认为模板类的实例仍然是抽象的

是的。

即使派生的类实现了这个抽象方法。

没有。

你没有覆盖MyClass::MethodT::Method 无关。

让我们暂时摆脱模板,因为它们无关紧要并且会分散问题的注意力:

class MyClass {
    virtual void Method() = 0;
};

class MyTemplateData {
    void Method() {
    }
};

class MyClassT : public MyClass, public MyTemplateData {
};

继承树如下:

MyClass  MyTemplateData
    \       /
     \     /
    MyClassT

Method 必须在左侧实现;也就是说,在MyClassMyClassT或介于两者之间。右边是一个不相关的继承分支:当你创建MyTemplateData::Method时,它并没有覆盖MyClass::Method,因为MyTemplateDataMyClass之间没有继承关系。

覆盖仅适用于直线。

【讨论】:

  • 好的,如果这是真的,这是我的一个糟糕的设计,我应该改变它。
  • 你说必须实现左分支才能编译。只是为了好玩,我尝试了以下方法:我将using T::Method; 包括在我的MyClassT 课程中。这应该(如果 T 实现了这个方法)使这个方法在 MyClassT 中可用,但即使这样也没有编译。
  • @bkausbk:“使其可用”与覆盖它不同。
猜你喜欢
  • 2014-11-02
  • 1970-01-01
  • 1970-01-01
  • 2016-11-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-30
  • 1970-01-01
相关资源
最近更新 更多