我试图了解标准是否有一个简单的CRTP模式。
下面的代码编译并按预期工作(在clang上)。
但我对相关标准章节的理解是这样的 虚拟功能CRTP的实例化点< Derived,Base> :: DoSomething() 应该在代码的点(B),其中Derived的完整声明不可用。 因此,内部typedef类型也不应该可用。
有人可以指出验证此代码的相关标准章节吗?
换句话说,就是说在这种情况下虚拟函数被实例化了 ATFER点C? 非常感谢您提供任何见解。
弗朗西斯
//-------------------------
// START CODE
#include <iostream>
struct Type1 {};
struct Type2 {};
struct Base
{
virtual ~Base() {}
virtual void DoSomething() = 0;
};
template< typename T, typename U >
struct CRTP : U
{
virtual void DoSomething() { DoSomething( typename T::Type() ); }
void DoSomething( Type1 ) { std::cout << "1\n"; }
void DoSomething( Type2 ) { std::cout << "2\n"; }
};
// (A) point of inst. of CRTP< Derived, Base > ( 14.7.1.4 ) ??
// (B) point of inst. of CRTP< Derived, Base >::DoSomething() (14.6.4.1.4 ) ??
struct Derived : CRTP< Derived, Base >
{
typedef Type2 Type;
};
// (C)
int main()
{
Base * ptr = new Derived;
ptr->DoSomething();
delete ptr;
}
// END CODE
//-------------------------
相关(?)标准段落:
14.6.4.1 4如果隐式实例化虚函数,其实例化点紧跟其封闭类模板特化的实例化点。
14.7.1 4如果在需要完全定义的对象类型的上下文中使用类类型,或者如果类类型的完整性可能影响程序的语义,则会隐式实例化类模板特化。
14.7.1 9实现不应隐式实例化函数模板,成员模板,非虚拟成员函数,成员类或不需要实例化的类模板的静态数据成员。如果虚拟成员函数不会被实例化,则实现是否隐式实例化类模板的虚拟成员函数是未指定的。
答案 0 :(得分:5)
这似乎是编译器推迟CRTP<Derived, Base>::DoSomething()
实例化直到翻译单元结束的结果,因为它允许这样做(见CWG issue 993)。
CRTP<Derived, Base>
肯定在Derived
的定义之前被实例化(§14.6.4.1[temp.point] / p4,所有引用都是N3936):
对于类模板特化,类成员模板 专门化,或类的类成员的专业化 模板,如果由于它而隐式实例化特化 如果是,则从另一个模板专门化中引用 引用专业化的上下文取决于a 模板参数,如果未实例化特化 在封闭模板的实例化之前,重点是 实例化紧接在实例化之前 封闭模板。否则,这样的实例化点 特殊化紧接在命名空间范围声明之前或 引用专业化的定义。
是否需要实例化CRTP<Derived, Base>::DoSomething()
取决于要求成员定义存在的上下文中引用的短语的含义(§14.7.1[temp.inst] ] / P2)。所有非纯虚函数都是使用过的(§3.2[basic.def.odr] / p2),并且“每个程序应该只包含每个非内联函数或该程序中使用的变量的一个定义” (§3.2[basic.def.odr] / p4);是否计为“在需要成员定义存在的上下文中引用”尚不清楚。
(即使它不是 required 实例化,但编译器仍然可以根据§14.7.1[temp.inst] / p11自由实例化它 - “无论是否未指定如果虚拟成员函数不会被实例化,则实现隐式实例化类模板的虚拟成员函数。“。)
如果CRTP<Derived, Base>::DoSomething()
确实被实例化,那么情况将由§14.6.4.1[temp.point] / p5和p8(强调我的)覆盖:
5如果虚函数被隐式实例化,那么它的意思是 实例化紧跟在实例化之后 它封闭的类模板专业化。
8 功能模板的专业化,成员函数模板, 或成员函数或类模板的静态数据成员可以 在翻译单元中有多个实例化点,和 除了上面描述的实例化点之外,对于任何 这种专业化有一个实例化点 翻译单位,翻译单位的结尾也被认为是 实例化点。类模板的专门化有 翻译单元中的大多数实例化点。一个 任何模板的特化都可能有实例化点 多个翻译单位。 如果有两个不同的实例化点 根据一个给出模板专业化的不同含义 定义规则(3.2),该程序是不正确的,没有诊断 必需的。强>
也就是说,它有两个实例化点,一个在CRTP< Derived, Base >
的实例化之后,一个在翻译单元的末尾。在这种情况下,在实例化的两个点上,typename T::Type
的名称查找会产生不同的结果,因此程序格式错误,无需诊断。
答案 1 :(得分:0)
使用new Derived
会导致Derived
类被实例化。
更正:
Derived
本身不是模板,因此需要立即使用其结构布局和包含的成员声明。这导致CRTP<Derived,Base>
在派生定义之后立即实例化。当我有更多时间时,我将不得不查看正式标准;但重点仍然是CRTP的实例化只能确定结构和可用成员,而不是成员职能的主体;当它这样做时,它知道Derived类的结构和成员。成员函数在使用之前不会被实例化(这里是构造函数),并且它当时已经有了类本身。要查找的另一件事是Derived的构造函数,因为它不是模板,是在类之后立即生成,还是仅在需要时生成。如果是前者,可以通过使用伪参数派生Derived来使其变得懒惰。但这并不影响这个具体问题:无论是在
Derived
之后还是在main
之后,在解析Derived
声明之前,函数实例化仍然是而不是
这导致CRTP<Derived,Base>
被实例化。但在这两种情况下,它只是所需的类结构,而不是任何成员的实际代码。擦除所有内联函数体,此时您将看到没有问题。
现在使用派生的默认构造函数,因此隐式实例化Derived::Derived()
。实例化的重点紧跟main
的定义。
在实例化Derived::Derived()
时,它需要CRTP<Derived,Base>::CRTP()
。它与需要它的模板实例化在同一点实例化。该构造函数需要所有虚函数,因此DoSomething()
再次实例化,与实例化的实例化相同。根据所有成员(不是函数体)的所有声明,您可以看到在完全定义完全呈现的Derived类之后,所有这些都发生得很好。
这是缺少的洞察力:类定义不包括成员函数定义,即使它们是在类定义的词汇封闭区域内给出的。记住定义和声明之间的区别,分别用于类和函数。