Oracle Solaris Studio 12.4 C++ 5.13 编译器在实施某些 C++ 规则时比过去的编译器严格得多。规则实施错误导致早期的编译器无法正确处理某些有效代码。本节说明更严格的实施,提供错误代码的示例,并显示如何修复代码。在所有这些例子中,您都可以更改代码,随后该代码应在新旧编译器中正常运行。
以前的编译器不解析模板定义,直到实例化模板后才会进行解析,这会产生以下后果:
以前的编译器直到定义模板后才允许使用未声明的名称。这可能会因明显的冲突而导致无效代码被接受,有效代码被拒绝,或使用错误的声明。
无法诊断出未实例化的模板中的无条件错误。
只有在实例化模板 T 后,才会实例化模板 T 的定义中不依赖于模板参数的名称。这些名称应在定义 T 时隐式实例化。
示例 1:
template< class T > int f(int i = j) // j is not visible { return i; } int j = 2; int main() { return f<int>(); }
早期的编译器接受此代码,因为只有在定义了 j 后才会解析模板 f。
解决方法:在定义模板之前声明 j。
示例 2:
#include <stdio.h> void f(double d) { printf("f(double)\n"); } template< class C > struct B { B() { f(1); } // f is not dependent on template parameter }; void f(int d) { printf("f(int)\n"); } int main() { B<int> b; // should print "f(double)" }
解决方法:确保在定义模板之前进行模板所依赖的任何声明。
在模板定义中查找未限定的名称时,不应检查依赖于模板参数的基类。以前的编译器对名称的查找不正确。
示例:
template <typename T> struct Base { }; template <typename T> struct Derived : Base <T> { Derived() : Base() { } // unqualified Base should not be found }; Derived <int> x; template <typename T> struct Derived2 : Base <T> { Derived2() : Base<T>() { } // OK }; Derived2<int> x2; int main() { }
在类 Derived 中,使用未限定的 Base 而没有模板参数是无效的。
解决方法:在类 Derived2 中,这种用法是正确的。
您不能再重新声明模板参数的名称。
示例:
template <typename T> class A { typedef int T; }; // re-declare T
解决方法:为模板参数或本地名称选择其他名称。
不再允许使用 1998 之前的不带 template<> 的显式专门化声明样式。
示例:
template <typename T> class A { static T m; }; int A<int>::m = 0; // now an error
解决方法:
template<> int A<int>::m = 0;
请参阅独立的模板定义。“独立模板定义”编译模型现在更严格了。如果要创建的 X.cc 文件包含在头文件 X.h 中声明的模板的定义,必须特别小心,不要在该文件中添加其他任何内容(即,除了与定义直接相关的项目之外的任何内容)。现在如果违反了此规则,则可能得到“多个定义”错误。
示例:
% cat extdef.h template <typename T> T foo(T); % cat extdef.cc #include "extdef.h" int main() { foo(1); } % CC extdef.cc -template=extdef "extdef.cc", line 3: Error: main() already had a body defined. 1 Error(s) detected.
解决方法:从 X.cc 文件中删除模板定义不需要的任何内容。请记得 X.cc 文件无需显式编译,因为只要包括 X.h 文件,就会自动包括它。
如果需要进行其他更改,可考虑使用 -template=no%extdef 选项进行编译。此行为是其他编译器的缺省行为,现在也是此发行版的缺省行为。
在 C++ 中从不允许隐式声明类型 int,但早期的编译器有时允许声明并显示警告。由于这干扰了正常的模板处理过程,因此编译器不再假定您要声明类型 int。
示例:
static i = 0; // now an error
解决方法:在声明中显式提供类型。
如果类 C 中函数的友元声明或类 T 是 T 的第一个声明,以前的编译器会错误地将 T 的声明插入周围作用域。Oracle Solaris Studio 12.4 C++ 5.13 编译器不再进行此操作,因为这会导致对有效程序进行错误解释。
示例:
class A { friend class B; // not previously declared friend void foo(); // not previously declared B* bar() // Error: B is not defined. { foo(); // Error: The function "foo" must have a prototype. return 0; } };
解决方法:在将类声明为友元的类声明之前,在该类之外的作用域中声明友元函数或类。
class B; void foo(); class A { friend class B; // refers to prior declaration friend void foo(); // refers to prior declaration B* bar() // OK { foo(); // OK return 0; } };
在相关函数调用中查找名称时,以前的编译器会错误地忽略作用域中的静态函数。Oracle Solaris Studio 12.4 C++ 5.13 编译器现在以同样的方式处理 static 和 extern 函数。
示例:
// previous compiliers ignored this bar() in dependent name lookup static int bar(int) { return 1; } int bar(long) { return 0; } template <typename T> int foo(T t) { // function call depends on template argument return bar(t); } int main() { return foo(0); }
使用以前的编译器编译时,程序会返回 0。使用 Oracle Solaris Studio 12.4 C++ 5.13 编译器时,程序将返回 1。
如果在名称查找中只找到静态函数,以前的编译器会发出类似如下的错误
"Reference to static bar(int) not allowed in template foo(int), try using -features=tmplrefstatic."
请参见前面的示例,删除 extern 函数 bar(long)。编译器现在只在 -compat=5 模式下发出警告,因为该代码在技术上是对 C++03 标准的扩展。在 C++11 模式中,将接受此代码而不显示任何提示,因为它是有效的 C++11。
仍接受选项 -features=no%tmplrefstatic,但它没有实际作用,因为始终允许对模板中的静态对象的引用。