Oracle Solaris Studio 12.2 发行版的新增功能

C++

更正大型十进制整型常量的解释

C++ 标准规定,无后缀的十进制整型常量当作 int 处理(如果值符合 int),否则当作 long int 处理。如果值不符合 long int,结果将不确定。

在 32 位模式中,类型 intlong 具有相同的大小和数据范围。C++ 编译器遵循 1990 C 标准规则,将 INT_MAX+1 和 LONG_MAX 之间的值当作无符号的 long 处理。这种处理在某些程序中会产生意外的结果。

1999 C 标准更改了无后缀的十进制整数的规则,这样它们就永远不会被当作无符号的类型来处理。类型为 intlonglong long 中第一个可以表示值的类型。

C++ 编译器在标准模式中遵循 C99 规则,但在 -compat=4 模式中继续遵循 C90 规则。(在 -compat=4 模式中,编译器的行为类似于 C++ 4.2 编译器。)

如果您希望大型十进制整数被当作无符号的整数,则简便的解决方法是使用 uU 后缀。同样,可以对其他类型使用其他后缀。例如:


              // note: 2147483648 == (INT_MAX+1)
              2147483648     // (signed) long long
              2147483648LL   // (signed) long long
              2147483648U    // same as 2147483648u

多义性:构造函数调用或指向函数的指针

某些 C++ 语句有可能解释成声明或者表达式语句。C++ 消除歧义规则为:如果一个语句可以处理成声明,那么它就是声明。

早期版本的编译器会错误解释类似于下面的代码:


          struct S {
            S();
          };
          struct T {
            T( const S& );
          };
          T v( S() );    // ???

编程人员也许本来打算在最后一行定义变量 v,并且用类型为 S 的临时变量对它进行初始化。早期版本的编译器会这样解释这条语句。

但是在声明环境里,构造符号 "S()" 也可以是抽象声明符(不带标识符),表示“没有返回值类型为 S 的参数的函数”。在这种情况下,该语句会自动转换为函数指针 "S(*)()"。这样该语句仍可作为函数 v 的声明,该函数有一个函数指针类型的参数,返回类型为 T 的值。

当前版本的编译器可以正确地解释该语句,但这未必是编程人员所需要的结果。

可以使用两种方法来修改上述代码以便不产生歧义:


          T v1( (S()) );  // v1 is an initialized object
          T v2( S(*)() ); // v2 is a function

第一行中另加的圆括号表明它不是 v1 作为函数声明的有效语法,所以它的唯一可能解释是“利用类型为 S 的临时值进行初始化的类型为 T 的目标”。

同样,构造符号 "S(*)()" 不可能是一个值,所以它的唯一可能解释是函数声明。

第一行也可以改写为:

T v1 = S();

虽然这时语句含义非常清楚,但这种形式的初始化有时会创建一个额外的临时变量,而一般情况下不会发生这种情况。

建议不要编写与下面语句类似的代码,因为它的含义不清楚,不同的编译器可能会提供不同的结果。

T v( S() ); // not recommended

不再忽略模板语法错误

下面的模板语法是无效的,但 Sun C++ 编译器 4 和 5.0 版并不报告这个错误。在标准模式(缺省模式)下编译时,C++ 编译器 5.1 版本及以后的所有版本都会报告这个语法错误。


        template<class T> class MyClass<T> { ... }; // definition error
        template<class T> class MyClass<T>; // declaration error

在这两种情况下,MyClass<T> 中的 <T> 无效,必须删除,如下例所示:


        template<class T> class MyClass { ... }; // definition
        template<class T> class MyClass; // declaration

如果将 -xipo 或 -xcrossfile 与 -instances=static 组合,链接会失败

模板选项 -instances=static(或 -pto)在与 -xcrossfile-xipo 选项组合时无效。使用该组合的程序会经常发生链接失败。

如果使用 -xcrossfile-xipo 选项,请使用缺省的模板编译模型 -instances=global 进行替代。

通常,不要使用 -instances=static(或 -pto)。它不再有任何优点,此外,C++ 用户指南中还对其缺点进行了说明。

跨语言链接错误

-xlang=f77 命令行选项导致编译进程遇到链接程序错误。要避免该错误,并仍包含相应的运行时库,应改为使用 -xlang=f77,f90 进行编译。

名称重整链接问题

以下情况会导致链接问题。

调试工具错误地报告成员函数有多余的前导参数

在兼容模式 (-compat) 下,C++ 编译器会错误地重整成员函数指针的链接名称。此错误会导致 demangler 以及其他一些调试工具(例如 dbx 和 c++filt)报告该成员函数有多余的前导参数,该参数包括对该成员函数所在类的引用。要更正此问题,请添加 -Qoption ccfe -abiopt=pmfun1 标志。请注意,使用这个标志进行编译的源代码可能会与不使用此标志编译的源代码在二进制上不兼容。在标准模式(缺省模式)下,不会出现该问题。

不支持引用模板中的非全局名称空间目标

如果您使用 -instances=extern 编译,则使用模板和静态对象的程序会出现未定义符号的链接时错误。使用缺省设置 -instances=global 则不会出现问题。编译器不支持对模板中的非全局名称空间作用域目标的引用。请看以下示例:


      static int k;
      template<class T> class C {
              T foo(T t) { ... k ... }
      };

在本示例中,一个模板类的成员引用了静态名称空间作用域的变量。请记住,名称空间作用域包含文件作用域。编译器不支持模板类的成员引用静态名称空间作用域的变量。另外,如果模板在其他的编译单元实例化,那么每个实例都会指向不同的 k,这破坏了 C++ 一次定义规则,代码的行为将会不可预测。

下面的方法也是可行的,但这取决于您如何使用 k,以及它应有的功能。第二个选项仅可供属于类成员的函数模板使用。

  1. 可以为变量提供外部链接属性:


                  int k; // not static 

    所有的实例都使用同一个 k

  2. 也可以使这个变量成为类的静态成员:


                template<class T> class C {
                        static int k;
                        T foo(T t) { ... k ... }
                };	

    静态类成员具有外部链接属性。每个 C<T>::foo 的实例都使用不同的 k。而 C<T>::k 的一个实例可以被其他函数共享。此选项可能是您需要的选项。

名称空间内的 #pragma align 需要重整名称

在名称空间内使用 #pragma align 时,必须使用重整名称。例如,在下面的代码中,#pragma align 语句是无效的。要更正此问题,应将 #pragma align 语句中的 a、bc 替换为其重整名称。


        namespace foo {
          #pragma align 8 (a, b, c) // has no effect
          //use mangled names: #pragma align 8 (__1cDfooBa_, __1cDfooBb_, __1cDfooBc_)
          static char a;
          static char b;
          static char c;
        }

函数重载解决方案

早期 C++ 编译器发行版并不支持 C++ 标准要求的函数重载。当前发行版修正了调用重载函数时出现的许多错误。具体来讲,当函数调用确实出现多义性时,编译器有时会选择某个函数;或者在函数调用实际上未产生多义性时,编译器会发出警告,指出此调用具有多义性。

采用某些解决方法来避免多义性消息已经没有必要了。也许您会看到以前未报告的新多义性错误。

导致函数调用多义性的一个主要原因在于仅重载内置类型的子集。


      int f1(short);
      int f1(float);
      ...
      f1(1); // ambiguous, "1" is type int
      f1(1.0); // ambiguous, "1.0" is type double

要解决此问题,要么根本不重载 f1,要么重载所有未提交的类型:int、unsigned int、long、unsigned long 和 double。(也许还有 long long、unsigned long long 以及 long double 类型)。

导致多义性的另一个主要原因在于:类中的类型转换函数,尤其是当您也重载了运算符或构造函数时。


      class T {
      public:
              operator int();
              T(int);
              T operator+(const T&);
      };
      T t;
      1 + t // ambiguous

该操作有多义性是因为它可能被处理成:


        T(1) + t     // overloaded operator
       1 + t.operator int()    // built-in int addition

可以提供重载运算符,也可以提供类型转换函数,但同时提供它们则会产生多义性。

实际上,类型转换函数自身也经常会导致多义性,而且往往在不应该进行转换时进行转换。如果您确实需要类型转换,最好使用命名的函数,而不是类型转换函数。例如,使用 int to_i t();,而不是 operator int();

更改后,1 + t 操作就不再具有多义性了。它只能解释为 T(1) + t。如果您希望有其他的解释,必须写入 1 + t.to_int()