使用继承类从模板类继承

Inheriting from a template class using the inheriting class

本文关键字:继承      更新时间:2023-10-16

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

#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不确定,就像我预期的那样。

这里的一般概念称为奇怪的重复曲线模式 crtp 。进行搜索将获得很多命中。请参阅: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(替换失败不是错误),甚至可以制作没有实例化的模板,因为用给定类型无法完成事情。

使用template class IFoo的定义,编译器不需要知道Foo的大小即可布置IFoo<Foo>

Foo将是此上下文中的不完整类(不是"未定义"或"未确定"),并且可以使用任何不完整类型的方式使用。出现在成员函数参数列表中是可以的。将成员变量声明为Foo*是可以的。将成员变量声明为Foo是禁止的(需要完整的类型)。

编译器如何创建代码?

回答这个问题与回答以下问题相同:编译器如何编译

struct Type;
Type func(Type);

实时示例

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

答案很简单:实际上使用该不存在的类型没有代码可以编译。由于没有代码要编译,该如何失败?

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

让我们分析编译器在执行此操作时看到的内容:

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<Foo>::stuffFoo的使用点已完成。那里也没有魔法。


为什么您不能在IFoo内声明T成员变量?

简单,出于同样的原因,该代码不会编译:

struct Bar;
Bar myBar;

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