C++ 虚拟函数、重写它们的类模板和不完整的类型 让我们考虑下面的代码片段(扩展到多个文件):
基础h:C++ 虚拟函数、重写它们的类模板和不完整的类型 让我们考虑下面的代码片段(扩展到多个文件):,c++,C++,基础h: #include <iostream> class Base { public: virtual void foo () = 0; }; template <class D> class BaseImpl : public Base { public: BaseImpl () : d (new D ()) {} virtual ~BaseImpl () { delete d; } void foo () override; private:
#include <iostream>
class Base
{
public:
virtual void foo () = 0;
};
template <class D>
class BaseImpl : public Base
{
public:
BaseImpl () : d (new D ()) {}
virtual ~BaseImpl () { delete d; }
void foo () override;
private:
D * d;
};
template <class D>
void BaseImpl <D>::foo ()
{
std::cout << d->value << std::endl;
}
main.cpp:
#include "Derived.h"
int main ()
{
Derived d;
d.foo (); // [1] error
((Base *) & d)->foo (); // [2] fine
}
此示例在第[1]行的CLang(Apple LLVM版本10.0.0(CLang-1000.11.45.5))上产生编译器错误,如下所示:
g++-c-std=c++11 main.cpp-o main.o
在main.cpp中包含的文件中:1:
在包含自./Derived.h的文件中:1:
./Base.h:22:17:错误:成员访问不完整的“数据”类型
std::cout value示例程序格式不正确
首先,检查任何模板的定义以获得与所有其他代码一样有效的语法,但是除非在模板被实例化之前,不需要C++实现来检查依赖于模板参数的任何部分的语义。(如果没有可能使专门化有效的模板参数,即使没有任何实例化,实现也可能报告语义错误。)
因此,如果main.cpp生成的翻译单元没有实例化函数模板专门化BaseImpl::foo()
,那么程序就可以了。但是这个模板专门化具体是在什么时候实例化的呢
除非类模板或成员模板的成员已显式实例化或显式专门化,否则当在要求成员定义存在的上下文中引用专门化时,或者如果成员定义的存在影响语义,则隐式实例化成员的专门化是节目的一部分
定义存在的主要要求来自ODR规则:
每个程序应包含该程序中odr在丢弃语句外使用的每个非内联函数或变量的一个定义;无需诊断……内联函数或变量应在每个转换单元中定义,其中odr在丢弃语句外使用
在这里,我们说专门化BaseImpl::foo()
是否为“内联”并不重要。ODR规则的两种风格最终都说,如果专门化是ODR使用的,那么必须定义它(以某种方式),这意味着专门化是隐式实例化的
对于不同种类的实体,技术术语“odr使用”有不同的定义(有时这些定义会重叠)。引用[basic.def.odr]/5-8中稍微超出必要的内容来说明一点:
变量x
的名称显示为可能计算的表达式ex
是ex
使用的odr,除非
如果结构化绑定显示为可能的计算表达式,则使用odr
*如果此
显示为潜在的计算表达式(包括非静态成员函数体中的隐式转换结果),则使用此
如果虚拟成员函数不是纯函数,则使用odr。如果函数由可能计算的表达式命名,则使用odr。类的非放置分配或释放函数是该类构造函数的定义使用的odr。类的非放置释放函数是该类的定义使用的odr该类的析构函数,或者通过在虚拟析构函数的定义点进行查找来选择
类中的赋值运算符函数是[class.copy.assign]中指定的另一个类的隐式定义的复制赋值或移动赋值函数所使用的odr。类的构造函数是[dcl.init]中指定的odr。如果类的析构函数可能被调用,则使用odr
请注意“使用odr”的所有这些定义将属性与某个特定的表达式或定义相关联,除非有人说非纯的虚拟函数仅由现有的odr使用。因此,在大多数情况下,隐式实例化是因为这些特定的其他表达式和定义需要模板专门化。这对于实例化而言意味着什么g虚拟功能在和中有所阐明:
如果虚拟成员函数不会被实例化,则未指定实现是否隐式实例化类模板的虚拟成员函数
如果虚拟函数是隐式实例化的,则其实例化点紧跟在其封闭类模板专用化的实例化点之后
因此,如果main.cpp根本没有提到foo
,但它仍然需要类BaseImpl
的实例化,而Data
仍然是一个不完整的类型,那么程序是否有效将是未知的!所以即使这样也可能不是一个好主意
顺便说一句,这就是为什么允许来自Derived.cpp的翻译单元实例化BaseImpl::foo()
。和g++,以及大多数使用公共“vtable”的编译器虚拟函数的策略,将实例化任何所需的类模板成员,这些成员是一个类的最终重写,只要该类的任何构造函数(显式或隐式)被调用已定义,因为它需要构建包含这些函数的vtable,以便构造函数可以将类对象的内部vptr设置为指向vtable
但我一开始说它的格式不正确,因为“否则将不会被实例化”所暗示的另一部分。另一句可能暗示BaseImpl::foo()
is odr used的句子是“如果一个函数是由一个潜在的计算表达式命名的,那么它就是odr used”。在引用之前不久,我们发现“由表达式命名的函数”也是一个技术定义,在:
函数由表达式命名,如下所示:
- 如果函数的名称出现在表达式中,则该函数的名称由该表达式命名(如果该函数是唯一的)
#include "Derived.h"
struct Data
{
int value = 123;
};
Derived::Derived () {}
Derived::~Derived () {}
#include "Derived.h"
int main ()
{
Derived d;
d.foo (); // [1] error
((Base *) & d)->foo (); // [2] fine
}
g++ -c -std=c++11 main.cpp -o main.o
In file included from main.cpp:1:
In file included from ./Derived.h:1:
./Base.h:22:17: error: member access into incomplete type 'Data'
std::cout << d->value << std::endl;
^
main.cpp:7:5: note: in instantiation of member function 'BaseImpl<Data>::foo' requested here
d.foo (); // [1]
^
./Derived.h:3:8: note: forward declaration of 'Data'
struct Data;
^
1 error generated.
g++ -c -std=c++11 main.cpp -o main.o
In file included from Derived.h:1:0,
from main.cpp:1:
Base.h: In instantiation of 'void BaseImpl<D>::foo() [with D = Data]':
main.cpp:7:10: required from here
Base.h:22:13: error: invalid use of incomplete type 'struct Data'
std::cout << d->value << std::endl;
^
In file included from main.cpp:1:0:
Derived.h:3:8: error: forward declaration of 'struct Data'
struct Data;
^