【问题标题】:How can I force a compiler error if an un-overridden virtual method is called?如果调用未覆盖的虚拟方法,如何强制编译器错误?
【发布时间】:2014-08-27 16:53:35
【问题描述】:

在用 C++ 编写模板基类时,这是一个关于样式和安全性的相当普遍的问题。不过,请耐心等待,最后有一个具体问题......

我有一个模板基类,当类型 T 是原始类型(int、float 等)时,它完全实现了所需的功能。但是,如果 T 不是原始的(例如,如果 T 是一个需要使用一组特定参数调用构造函数的类),则需要重写某些方法,因此它们在模板。模板还包含一个纯虚方法,它强制它是抽象的,因此为了使用它必须从以下位置派生:基于原始类型的派生类可以使用提供的所有方法,并且只覆盖纯虚拟方法,而基于非原始类型的派生类应该覆盖所有虚拟方法。

例如:

template <typename T> class myTemplate
{
public:
    // Constructor:
    myTemplate<T>();

    // Destructor:
    virtual ~myTemplate();

    // General function, valid for all classes derived from this:
    void printMe()
    {
        printf("Hello, I'm from the template\r\n");
    }

    // Primitive function, must be overridden for non-primitive types:
    virtual T DoSomething(const T& myClass)
    {
        T result = result + static_cast<T>(1);
        return result;
    }

    // Primitive function, must be overridden for non-primitive types:
    virtual T DoSomethingElse(const T& myClass)
    {
        T result = result - static_cast<T>(1);
        return result;
    }

    // Pure Virtual function, must be overridden in all cases:
    virtual void RefrainFromDoingAnythingAtAll(const T& myClass) = 0
};


class myIntegerClass : public myTemplate<int>
{
public:
    virtual void RefrainFromDoingAnythingAtAll(const int& myInteger) {} 
};

假设现在我想创建一个派生类,我希望在其中调用“DoSomething”,但无法设想在任何情况下“DoSomethingElse”会有用。因此,我想重新实现“DoSomething”,但不打扰“DoSomethingElse”。然而,如果在某个时候派生类的“DoSomethingElse”方法确实被调用(或者是因为我真的想调用“DoSomething”但错误地写了错误的东西,或者因为出现了我遇到的情况之前没有想到),然后我想发出一个编译器警告来提醒我,除非我先重新实现 'DoSomethingElse',否则我不能这样做。

例如:

class myWatermelonClass : public myTemplate<Watermelon>
{
public:
    virtual Watermelon DoSomething(const Watermelon &myWatermelon)
    {
          // Just return a copy of the original:
          Watermelon anotherWatermelon(myWatermelon);
          return anotherWatermelon;
    }

    virtual Watermelon DoSomethingElse(const Watermelon &myWatermelon)
    {
          // This routine shouldn't ever get called: if it does, then 
          // either I've made an error or, if I find that I really need
          // to reimplement it after all, then I'd better buckle down
          // and do it.
          < How do I make the compiler remind me of this? >
    }        

    virtual void RefrainFromDoingAnythingAtAll(const Watermelon& myWatermelon) {}   
};

显然我知道标准的#error 和#warning 编译器指令,但是如果我使用其中之一,那么每次编译时都会标记错误(或警告)。我想要的是确保在我疏忽调用时在编译时给出错误

DoSomethingElse(aSpecificWatermelon);

来自我代码中的某处,但不是其他情况。有没有办法做到这一点?或者这只是一个根本上糟糕的设计?

【问题讨论】:

  • 你检查过纯虚方法吗?
  • “基于原始类型的派生类可以使用提供的所有方法并仅覆盖纯虚拟方法,而基于非原始类型的派生类应覆盖所有虚拟方法” -看起来您应该为非原始类型使用从 myTemplate 继承的单独类。在 mytemplate 中,所有虚函数都应该是纯函数,除一个之外的所有函数都应该在新类中实现。

标签: c++ templates inheritance compiler-errors pragma


【解决方案1】:

我认为您在滥用模板和虚拟调度组合。例如,您的基类不会为任何不支持加法运算符且可从 int 构造的类型进行编译。每次myTemplate 被隐式特化,所有虚函数被编译,即使你在派生类中重写它们:

virtual T DoSomething(const T& myClass)
{
    T result = result + static_cast<T>(1); // compile error when T == Watermelon
    return result;
}

在您的情况下,您正在寻找的是显式模板专业化:

template <>
class myTemplate<Watermelon>
{
public:
    // General function, valid for all classes derived from this:
    void printMe()
    {
        printf("Hello, I'm from the Watermelon template specialisation\r\n");
    }

    virtual Watermelon DoSomething(const Watermelon &myWatermelon)
    {
        // Just return a copy of the original:
        Watermelon anotherWatermelon(myWatermelon);
        return anotherWatermelon;
    }

    // notice there's no DoSomethingElse!

    virtual void RefrainFromDoingAnythingAtAll(const Watermelon& myWatermelon) {}   
};

现在在 myTemplate&lt;Watermelon&gt; 的实例上调用 DoSomethingElse 会立即给您一个编译错误,因为没有这样的函数。

【讨论】:

  • 我认为如果我的实际课程更接近我给出的示例,那么这将是要走的路。然而,在实践中,我的实际 'myTemplate' 中没有 static_casts (恐怕这些是我脑海中第一个出现的事情,作为非原始类型需要覆盖的示例)但是确实有非常多的成员(其中绝大多数不需要被覆盖)和数据字段(当我刚刚做一个实验时)不是专门模板从通用模板继承的。跨度>
  • 那你的myTemplate 有虚拟会员吗?
  • 是的,但他们只是会员总数的一小部分。绝大多数成员函数不需要重写,所以我不想在我的专用模板或最终派生类中重新实现它们。
  • 然后分解基本类型。 myTemplateBase&lt;T&gt; 可以包含不需要覆盖的数据成员和函数。 myTemplate&lt;T&gt;myTemplate&lt;Watermelon&gt; 都可以从它派生出来并提供特定功能或实现虚拟功能。
  • 我知道这会起作用,但它比我希望的更麻烦。我希望语言中有一些东西可以简化这一点,但看起来没有。
【解决方案2】:

通过使用static_assert,当您尝试调用不符合特定条件的函数时,可能会导致编译错误。根本不需要虚函数。这是一个例子:

#include <iostream>
#include <type_traits>

using std::cout;

template <typename T>
struct myTemplate {
    void printMe() { cout << "Hello, I'm from the template\r\n"; }

    T DoSomething(const T& myClass)
    {
        static_assert(
          std::is_fundamental<T>::value,
          "DoSomething must be redefined in derived classes "
          "for non-fundamental types."
        );
        T result = myClass + static_cast<T>(1);
        return result;
    }

    T DoSomethingElse(const T& myClass)
    {
        static_assert(
          std::is_fundamental<T>::value,
          "DoSomethingElse must be redefined in derived classes "
          "for non-fundamental types."
        );
        T result = myClass - static_cast<T>(1);
        return result;
    }

    template <typename U>
    struct never_true { static const bool value = false; };

    void RefrainFromDoingAnythingAtAll(const T&)
    {
        static_assert(
            never_true<T>::value,
            "RefrainFromDoingAnythingAtAll must be redefined "
            "in derived classes."
        );
    }
};

struct Watermelon {
};

struct Lemon {
};

struct myIntegerClass : myTemplate<int> {
  void RefrainFromDoingAnythingAtAll(const int &) { }
};

struct myWatermelonClass : myTemplate<Watermelon> {
    Watermelon DoSomething(const Watermelon&)
    {
      return Watermelon();
    }

    Watermelon DoSomethingElse(const Watermelon&)
    {
      return Watermelon();
    }

    void RefrainFromDoingAnythingAtAll(const Watermelon &) { }
};


struct myLemonClass : myTemplate<Lemon> {
};

int main()
{
    myIntegerClass x;
    x.DoSomething(5); // works
    x.DoSomethingElse(5); // works
    x.RefrainFromDoingAnythingAtAll(5); // works
    myWatermelonClass y;
    y.DoSomething(Watermelon()); // works
    y.DoSomethingElse(Watermelon()); // works
    y.RefrainFromDoingAnythingAtAll(Watermelon()); // works
    myLemonClass z;
    z.DoSomething(Lemon()); // error
    z.DoSomethingElse(Lemon()); // error
    z.RefrainFromDoingAnythingAtAll(Lemon()); // error
}

【讨论】:

  • 注意:这仅在DoSomethingElse 不是虚函数时有效。否则,它将使myTemplate&lt;Watermelon&gt; 的断言失败。
  • 我喜欢这种 static_assert 方法的外观,但我想在接受答案之前尝试一下。如果我真正的 DoSomethingElse 模板函数包含 static_cast,@gwiazdorrr 的评论将是完全正确的,但在我的实际模板中它没有。
  • 遗憾的是我无法让它工作,因为即使成员被覆盖,static_assert 仍然会引发编译器错误。我想这就是@gwiazdorrr 的回答中提出的观点的重要性,“每次 myTemplate 隐式专门化时,所有的虚函数 will 都会被编译,即使你在派生类中覆盖它们”,而且他上面关于这个方案的评论只有在 DoSomethingElse 不是虚拟的情况下才有效。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-22
  • 1970-01-01
  • 2010-11-22
  • 1970-01-01
相关资源
最近更新 更多