【问题标题】:Inheriting from a template class using the inheriting class使用继承类从模板类继承
【发布时间】:2018-05-23 00:36:15
【问题描述】:

当我从一个类继承时,编译器必须知道基类的定义才能创建它。但是当我使用自己(继承类)从模板类继承时,编译器如何创建代码?它还不知道类的大小。

#include <iostream>

template <class T> class IFoo
{
public:
     virtual T addX(T foo, double val) = 0;
     // T memberVar; // uncomment for error
};

class Foo : public IFoo<Foo>
{
public:
    Foo(double value)
        : m_value(value) {}

    Foo addX(Foo foo, double b) override 
    { 
        return Foo(foo.m_value + b); 
    }

    double m_value;
};

int main()
{
    Foo foo1(1);
    Foo foo2 = foo1.addX(foo1, 1);

    std::cout << foo2.m_value;
}

首先我认为它有效,因为它是一个接口,但它也适用于常规类。

当我将模板存储为成员时,我收到一个错误,即 Foo 未定义,正如我预期的那样。

【问题讨论】:

  • memberVar 将使Foo (间接地)持有另一个自身的实例。从本质上讲,这将使sizeof(Foo) == 2 * sizeof(Foo)。所以当然行不通。 :-)
  • 感谢您的所有精彩回答。现在我很清楚了。

标签: c++ templates inheritance interface


【解决方案1】:

有了这个template class IFoo的定义,编译器不需要知道Foo的大小来布局IFoo&lt;Foo&gt;

Foo 在这种情况下将是一个不完整的类(不是“未定义”或“未声明”),并且可以使用任何不完整类型的方式使用。出现在成员函数参数列表中很好。将成员变量声明为 Foo* 很好。禁止将成员变量声明为Foo(需要完整类型)。

【讨论】:

    【解决方案2】:

    这里的一般概念称为Curiously Recurring Template PatternCRTP。搜索它会得到很多点击。见:https://stackoverflow.com/questions/tagged/crtp

    但是,有一个简单的解释可能会回答您的问题,而无需过多了解 CRTP。在 C 和 C++ 中允许以下内容:

    struct foo {
        struct foo *next;
        ...
    };
    

    或有两种类型:

    struct foo;
    struct bar;
    
    struct foo {
        struct bar *first;
        ...
    };
    
    struct bar {
        struct foo *second;
        ...
    };
    

    只要只使用指向structclass 的指针,就不必提供类型的完整定义。可以通过多种方式在此之上对模板进行分层,并且必须清楚地分别推理参数化模板的类型及其在模板中的使用。添加 SFINAE(替换失败不是错误),甚至可以制作没有实例化的模板,因为给定类型无法完成。

    【讨论】:

      【解决方案3】:

      编译器如何创建代码?

      回答这个问题与回答这个问题是一样的:编译器如何编译那个

      struct Type;
      Type func(Type);
      

      Live example

      如何定义一个不存在的类型,却又声明一个使用该类型的函数?

      答案很简单:没有可编译的代码实际上使用了不存在的类型。既然没有代码可以编译,怎么会失败呢?

      现在,您可能想知道您的代码与您有什么关系?如何让一个类可以将自己作为模板参数发送给它的父类?

      让我们分析一下编译器在你这样做时看到了什么:

      struct Foo : IFoo<Foo> { /* ... */ };
      

      首先,编译会看到:

      struct Foo ...
      

      编译器现在知道Foo 存在,但它是一个不完整的类型。

      现在,他看到了:

      ... : IFoo<Foo> ...
      

      它知道IFoo 是什么,它知道Foo 是一个类型。编译器现在只需要用该类型实例化 IFoo

      template <class T> struct IFoo
      {
           virtual T addX(T foo, double val) = 0;
      };
      

      实际上,它声明了一个类,其中声明了一个函数。您在上面看到声明具有不完整类型的函数是有效的。同样的情况也发生在这里。此时,您的代码是可能的,因为此代码是:

      struct Foo;
      template struct IFoo<Foo>; // instanciate IFoo with Foo
      

      所以真的没有巫术。


      现在让我们举一个更有说服力的例子。那怎么办?

      template<typename T>
      struct IFoo {
          void stuff(T f) {
              f.something();
          }
      };
      
      struct Foo : IFoo<Foo> {
          void something() {}
      };
      

      编译器如何在不完整的类型上调用something

      问题是:它没有。当我们使用something 时,Foo 是完整的。这是因为模板函数只有在使用时才会被实例化。

      还记得我们甚至可以使用模板来分离函数定义吗?

      template<typename T>
      struct IFoo {
          void stuff(T f);
      };
      
      template<typename T>
      void IFoo<T>::stuff(T f) {
          f.something();
      }
      
      struct Foo : IFoo<Foo> {
          void something() {}
      };
      

      太棒了!它是否开始看起来与您使用纯虚函数的示例完全相同?让我们进行另一个有效的转换:

      template<typename T>
      struct IFoo {
          void stuff(T f);
      };
      
      struct Foo : IFoo<Foo> {
          void something() {}
      };
      
      // Later...
      template<typename T>
      void IFoo<T>::stuff(T f) {
          f.something();
      }
      

      完成!我们稍后定义了函数,在Foo 完成之后。这就是发生的事情:编译器只会在使用时实例化IFoo&lt;Foo&gt;::stuff。而它的使用点,Foo 就完成了。那里也没有魔法。


      那为什么不能在IFoo 中声明一个T 成员变量呢?

      很简单,与这段代码无法编译的原因相同:

      struct Bar;
      Bar myBar;
      

      声明一个不完整类型的变量是没有意义的。

      【讨论】:

        猜你喜欢
        • 2018-01-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-09-29
        • 1970-01-01
        • 1970-01-01
        • 2014-06-09
        相关资源
        最近更新 更多