ISO/IEC 14882:2011之条款3.2——一次定义规则
1、任何一个翻译单元都不该包含对任一变量、函数、类类型、枚举类型或模板的多于一次的定义。
2、一个表达式是被潜在计算的,除非它是一个不被计算的操作数(条款5),或是一个不被计算的操作数的子表达式。名字作为一个潜在被计算的表达式而出现的一个变量是odr-used[译者注:使用一次定义规则的],除非它是满足出现在一个常量表达式中(5.19)的要求并且左值到右值的转换(4.1)被立即应用的对象。关键字this是odr-used的,如果它作为一个被潜在计算的表达式出现(包括作为一个非静态成员函数(9.3.1)的函数体中的隐式变换的结果)。一个虚成员函数是odr-used的,如果它不是纯虚函数。一个非被重载的函数,其名字作为一个潜在被计算的表达式或一组候补函数的一个成员出现,当从一个被潜在计算的表达式被引用时,如果被重载决议所选择,那么是odr-used的,除非它是一个纯虚函数并且其名字不被显式限定。[注:这涵盖了对命名函数(5.2.2),操作符重载(条款13),用户定义的转换(12.3.2),placement new(5.3.4)的分配函数,以及非默认初始化(8.5)的调用。一个拷贝构造器和搬移构造器是odr-used的,即使该调用实际上被实现省略。 ——注结束]一个类的分配或释放函数是odr-used的,通过出现在一个被潜在计算的表达式中的一个new表达式,在5.3.4和12.5中指出。一个类的一个释放函数是odr-used的,通过出现在一个被潜在计算的表达式中的一个delete表达式,在5.3.5和12.5中指出。一个类的一个非placement分配或释放函数是odr-used的,通过那个类的一个构造器的定义。一个类的一个非placement的释放函数是odr-used的,通过那个类的析构器的定义,或通过查找一个虚拟析构器(12.4)的定义点而被选择。一个类的一个拷贝赋值函数是odr-used的,通过另一个类的一个隐式定义的拷贝赋值函数,在12.8中指出。一个类的一个搬移赋值函数是odr-used的,通过另一个类的一个隐式定义的搬移赋值函数,在12.8中指出。一个类的一个默认构造器是odr-used的,通过默认初始化或值初始化,在8.5中指出。一个类的一个构造器是odr-used的,在8.5中指定。一个类的一个析构器是odr-used的,在12.4中指定。
3、每个程序应该恰好包含每个ord-used的非内联函数或变量的一个定义;不需要诊断信息。定义可以显式地出现在程序中,他可以在标准库或用户定义的库中找到,或(当适当时)它被隐式定义(见12.1、12.4和12.8)。一个内联函数应该在每个翻译单元中被定义,而在一个特定的翻译单元中,它是odr-used的。
4、一个翻译单元中需要一个类的恰好一个定义,如果那个类需要以类类型是完整的方式被使用。[例:以下完整的翻译单元是良好定义的,即使它没有定义X:
struct X; // 将X声明为一个结构体类型 struct X *x1; // 以指针形式使用X X* x2; // 以指针形式使用X
—— 例结束][注:声明和表达式的规则描述在需要完整的类类型的上下文中。]一个类类型T必须是完整的,如果:
——类型T的一个对象被定义(3.1),或
——一个类型T的非静态类数据成员被声明(9.2),或
——T被用作为一个new-expression中的对象类型或数组元素类型(5.3.4),或
——一个左值到右值的转换被应用到引用一个类型T的一个对象的glvalue[译者注:广义的左值](4.1),或
——一个表达式被(隐式或显式地)转换为类型T(条款4,5.2.3,5.2.7,5.2.9,5.4),或
——一个非空指针常量的一个表达式,并且具有除void*以外的类型,被转换为指向T的指针类型,或对T的引用类型,使用一个隐式转换(条款4),一个dynamic_cast(5.2.7),或一个static_cast(5.2.9),或
——一个类成员访问操作符被应用于类型T的一个表达式(5.2.5),或
——typeid操作符(5.2.8)或sizeof(5.3.3)操作符被应用于类型T的一个操作数,或
——带有一个类型T的返回类型或实参类型的一个函数被定义(3.1)或被调用(5.2.2),或
——带有类型T的一个基类的一个类被定义(条款10),或
——类型T的一个左值被赋值(5.17),或
——类型T是一个alignof表达式的主语[译者注:即操作数](5.3.6),或
——exception-declaration具有类型T,对T的引用,或指向T的指针(15.3)。
—— 注结束]
5、一个类类型(条款9),枚举类型(7.2)、带有外部链接的内联函数(7.1.2),类模板(条款14),非静态函数模板(14.5.6),一个类模板的静态数据成员(14.5.1.3),一个类模板的成员函数(14.5.1.1),或某些模板形参未被指定的模板特化,在一个所给的程序中可以有多于一个定义,每个定义在一个不同的翻译单元中出现,并且提供了满足以下要求的定义。给定这样的一个实体,命名为D,定义在多个翻译单元中,那么
——D的每个定义应该由相同符记序列构成;并且
——在D的每个定义中,根据3.4所查找的相应的名字,应该引用在D的定义的内部所定义的一个实体,或者应该引用相同的实体,在重载决议后(13.3),以及在匹配部分模板特化后(14.8.3),除了一个名字可能以内部连接或无连接引用一个const对象,如果该对象在D的所有定义中具有相同的字面量类型,并且该对象用一个常量表达式来初始化(5.19),并且对象的值(而不是地址)被使用,并且该对象在D的所有定义中具有相同的值;并且
——在D的每个定义中,相应的实体应该具有相同的语言连接;并且
——在D的每个定义中,被引用的被重载的操作符、对转换函数的隐式调用、构造器、operator new函数、以及operator delete函数,应该引用同一个函数,或者引用定义在D的定义内部的一个函数;并且
——在D的每个定义中,被一个(隐式或显式的)函数调用所使用的一个默认实参被对待为就好比其符记序列被展现在D的定义中一样;即,默认实参从属于上面所描述的三个要求(并且,如果默认实参具有带有默认实参的子表达式,那么该要求被递归应用)。[注:8.3.6描述了默认实参名字如何被查找]
——如果D是带有一个被隐式声明的构造器的一个类(12.1),那么它就好似构造器被隐式定义在每个作为odr-used的翻译单元中,并且在每个翻译单元中的隐式定义应该为一个基类或D的一个类成员调用相同的构造器。[例:
//翻译单元1: struct X { X(int); X(int, int); }; X::X(int = 0) { } class D: public X { }; D d2; // X(int)被D()调用 //翻译单元2: struct X { X(int); X(int, int); }; X::X(int = 0, int = 0) { } class D: public X { }; //X(int,int)被D()调用 // D()的隐式定义违背了ODR D d1;
——例结束]
如果D是一个模板,并被定义在多个翻译单元中,那么之前的要求既要应用到模板定义中所使用的模板封闭作用域的名字(14.6.3),也要应用到在实例化点上所依赖的名字(14.6.2)。如果D的定义满足所有这些要求,那么程序的行为应该就好像D只有一单个定义。如果D的定义不满足这些要求,那么行为是未定义的。