当我从类继承时,编译器必须知道基类的定义才能创建它。但是当我使用自己继承模板类(继承类)时,编译器如何创建代码?它还不知道班级的大小。
#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未定义,就像我预期的那样。
答案 0 :(得分:2)
通过template class IFoo
的定义,编译器无需知道Foo
的大小IFoo<Foo>
。
Foo
将是一个不完整的类(不是&#34;未定义&#34;或&#34;未声明&#34;)并且可以使用任何不完整类型的方式。出现在成员函数参数列表中就可以了。将成员变量声明为Foo*
很好。禁止将成员变量声明为Foo
(需要完整类型)。
答案 1 :(得分:2)
此处的一般概念称为奇怪的重复模板模式或 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;
...
};
只要使用指向struct
或class
的指针,就不必提供该类型的完整定义。可以通过各种方式在此基础上对模板进行分层,并且必须清楚地分别说明参数化模板的类型及其在模板中的使用。添加SFINAE(替换失败不是错误),甚至可以制作不实例化的模板,因为无法使用给定类型完成任务。
答案 2 :(得分:1)
编译器如何创建代码?
回答这个问题与回答这个问题是一样的:编译器如何编译那个?
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
?
Foo
时,something
已完成。这是因为模板函数仅在使用时才会被实例化。
请记住,即使使用模板,我们也可以将函数定义分开吗?
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>::stuff
。而使用它的地方Foo
已经完成了。也没有魔法。
为什么不能在T
内声明IFoo
成员变量呢?
简单,原因与此代码无法编译的原因相同:
struct Bar;
Bar myBar;
声明一个不完整类型的变量是没有意义的。