65.9K
CodeProject 正在变化。 阅读更多。
Home

Whidbey C++ 破坏性更改的原理

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (10投票s)

2005年2月6日

CPOL

15分钟阅读

viewsIcon

145869

对 Whidbey C++ 编译器更改所做的决定背后的解释、变通方法、场景和原理。

引言

Microsoft 在 Whidbey 的 C++ 编译器中进行了 20 多项破坏性更改。Microsoft 开发团队非常希望就这些更改获得反馈,因此提供了以下解释、变通方法、场景和更改编译器的决定背后的原理。

请注意,这是初步信息,可能会发生更改。

 

1. 指向成员的指针现在需要限定名称和 &。

受影响的用户场景

为旧版本编译器编写的代码,如果仅使用方法名,将产生错误

描述

这是标准 C++。符合标准要求,为了创建指向成员函数的指针,必须使用地址运算符 (&) 和方法的完全限定名称。 

客户变通方法

客户应使用方法的完全限定名称并使用地址运算符 (&) 来创建指向成员函数的指针。 当我们无法区分客户是忘记了函数调用中的括号还是想创建指向成员函数的指针时,这通常是错误的常见原因。  

原理

我们做出此强制执行(除了符合 C++ 标准之外)的原因是,在许多情况下,由于函数调用中丢失了括号,这会导致客户代码出现逻辑错误。 使用不带参数列表的函数名称会生成一个函数指针,该指针可转换为多种类型,因此代码会编译成功,但在运行时会出现意外行为。

2. __asm int 3 现在生成本机代码

受影响的用户场景

希望其代码生成 MSIL 并使用 __asm int 3 指令生成断点的客户需要修改其代码。 

描述

在 __asm int 3 被编译器转换为 CLR 断点指令之前,使用 /clr 编译时不会生成本机代码。 

客户变通方法

客户现在必须使用 __debugbreak,以便函数可以编译为 MSIL 并仍充当断点。 

原理

我们希望在定位 CLR 时,关于何时生成本机代码或托管代码,而不是特殊处理内联程序集代码,能够更加确定。 内联程序集代码应生成本机代码。

3. 不允许显式特化作为复制构造函数/复制赋值运算符

受影响的用户场景

依赖于其代码中显式模板特化的复制构造函数或复制赋值运算符的用户现在将收到编译器错误 C2299。 

描述

标准 C++ 禁止此行为,因此我们进行了更改以使其符合标准。 用户需要更新其代码。 

客户变通方法

客户的变通方法是停止将复制构造函数/复制赋值运算符设为模板函数,而是将其设为接受带有所需模板参数的类类型的常规函数。 任何通过显式指定模板参数调用复制构造函数/复制赋值运算符的代码都需要删除模板参数。 

原理

C++ 标准符合性始终是我们的优先事项之一;用户期望他们的代码具有可移植性。 我们进行了此更改以符合标准。 

4. 未特化的类模板不能用作基类列表中的模板参数

受影响的用户场景

在类的定义中,将未特化的模板类名用作基类列表中的模板参数的用户将看到编译器错误。 

描述

在基类列表中使用未特化的类模板名称作为模板参数是非法的。 在类内的其他地方,编译器会注入未特化的类型参数。 

客户变通方法

用户在基类列表中将模板类名用作模板参数时,需要显式添加模板类型参数。 

原理

C++ 标准符合性始终是我们的优先事项之一;用户期望他们的代码具有可移植性。 我们进行了此更改以符合标准。 

5. 嵌套类型的 using 声明现在是非法的

受影响的用户场景

具有嵌套类型 using 声明的客户现在将收到 C2885。 

描述

以前,用户可以通过 using 声明将嵌套类型的声明引入全局作用域。 标准 C++ 不允许这样做。 

客户变通方法

客户的变通方法是在想使用嵌套类型时,始终使用其完全限定名称。 typedef 也可以用于避免处处使用作用域解析运算符。 

原理

using 声明不能与嵌套类一起使用;C++ 标准规定,它可用于将类引入命名空间所包含的作用域,而不是类。 

6. 编译器不再允许使用旧语法的 const_cast 进行向下转换。

受影响的用户场景

用户错误地被允许使用 const_cast 进行向下转换(在层次结构中向下转换到更派生的类类型),我们 7.0 和 7.1 编译器允许这样做。 此类用户代码现在将遇到编译器错误 C2440。  

描述

这是不正确的代码。 根据定义,const_cast 只能用于移除/添加 cv 限定符。 其他任何使用都是不正确的。 

客户变通方法

客户的变通方法是连接 const_cast 与我们其他可以进行向下转换的转换(dynamic_cast, static_cast 或 __try_cast)一起使用。 

原理

C++ 标准指出,const_cast 应用于添加或移除 const volatile 限定符;允许其行为不同,对托管类型的特殊处理是错误的。 

7. 编译器不允许前向声明托管枚举

受影响的用户场景

声明但不定义托管枚举(/clr 或 /clr:oldSyntax)将导致编译器错误。 为 VC7.0 和 VC7.1 编译器编写的代码将中断。 

描述

这以前在 VC7.0 和 VC7.1 中可以编译,但不能保证能正确工作。 问题在于编译器无法正确识别枚举的基础类型。 

客户变通方法

客户应始终在声明时定义其托管枚举。 

原理

托管枚举可以具有不同的基础类型。 我们没有在枚举声明中指定基础类型的语法;此外,C++ 标准不允许枚举声明。 

8. 开关移除:/YX

受影响的用户场景

用户使用此开关为他们的代码生成自动预编译头支持。 它默认由 IDE 使用。 

描述

我们有其他开关可供用户使用,以添加更好的预编译头支持。 

客户变通方法

客户可以使用 /Yc 和 /Yu 来支持预编译头。 

原理

此开关有时行为不正确。 我们更希望客户使用 /Yc 和 /Yu,它们为用户提供了更多的控制权。 

9. 开关移除:优化 /Oa, /Op 和 /Ow

受影响的用户场景

用户需要从他们的项目中删除这些。 

描述

这些开关被其他开关取代或不再需要。 

客户变通方法

客户可以使用 /fp 开关进行浮点优化。 

原理

编译器选项 /Oa 和 /Ow 通常会导致生成糟糕的代码,而 /Op 被更好的 /fp 开关取代;这些为用户提供了更好的控制。 

10. 开关移除:/ML 和 /MLd

受影响的用户场景

依赖于静态链接到单线程 CRT 的用户。 

描述

不再存在单线程 CRT。 

客户变通方法

客户可以使用 /MT 和 /MTd 分别进行操作。 

原理

我们已停止制作单线程 C Runtime Library (CRT),因此我们不再需要此编译器选项。 

11. 开关移除:/G3 - /G7, /GB, /Gf, /GD, /GM

受影响的用户场景

用户需要从他们的项目中删除这些。 

描述

处理器特定的优化开关可以用我们的 /OX 系列开关取代。 

客户变通方法

客户可以使用我们的 /OX 系列优化开关实现相同的功能。 

原理

编译器选项 /G3 - /G7 针对特定处理器,其中大多数不再批量销售。 用户可以使用我们的 Ox 编译器选项实现相同的优化。 对于 64 位开发,我们添加了一个新的优化开关:/Favor:[ALL|AMD64|EM64T]。 编译器选项 /Gf 启用了字符串池化,但使用了可写内存,这是危险的;/GF 在更安全的只读内存中实现了相同的功能。 编译器选项 /GD 的功能可以用我们的其他优化选项实现;我们不需要特定的 DLL 优化。 编译器选项 /GM 和 /GB 已不再需要。 

12. 开关冲突:/clr 和 /MT

受影响的用户场景

用户需要将 /MT 替换为 /MD 来针对 .NET 的代码。 

描述

CRT 不支持静态链接托管应用程序。 

客户变通方法

客户需要将 /MT 更改为 /MD 并动态链接到 CRT。 

原理

C Runtime Library 不支持静态链接到托管应用程序。 所有托管应用程序都必须动态链接;因此,这两个编译器选项会发生冲突。 

13. 开关更改:/GS 默认开启。

受影响的用户场景

用户现在默认在其代码中获得缓冲区溢出检查。 

描述

这是一件好事。 如果您遇到此开关的错误,很可能是用户代码存在安全漏洞。 

客户变通方法

客户可以使用 /GS- 选项将其关闭。 

原理

我们希望我们的用户始终受益于 /GS 提供的安全检查。 

14. 开关更改:/Zc:wchar_t 默认开启

受影响的用户场景

当客户端代码与未用 Zc:wchar_t 编译的库链接时,这会破坏二进制兼容性。 

描述

这是标准 C++ 行为:wchar_t 变量默认是内置类型,而不是无符号短整数。 

客户变通方法

客户可以使用新引入的编译器开关 /Zc:wchar_t- 恢复到旧的非标准行为。 

原理

引入此编译器选项是为了纠正我们曾经发布的符合性差异,并仍然为用户提供恢复旧行为的方法。 我们希望我们的用户代码默认符合标准。 

15. 开关更改:/Zc:forScope 默认开启

受影响的用户场景

依赖于在块作用域结束后使用 for 作用域声明的变量的代码将中断,无法编译。 

描述

这是标准 C++ 行为:for 作用域声明的变量不会在 for 块作用域之外存在。 

客户变通方法

客户可以使用新引入的编译器开关 /Zc:forScope- 恢复到旧的非标准行为。 

原理

引入此编译器选项是为了纠正我们曾经发布的符合性差异,并仍然为用户提供恢复旧行为的方法。 我们希望我们的用户代码默认符合标准。

16. 强制对 vc 属性进行参数检查

受影响的用户场景

用户将命名属性以引号形式传递给属性构造函数(当类型不是字符串时)以及不带引号(当类型是字符串时)传递的用户名将停止编译。 

描述

以前,所有编译器属性都被解析为字符串,因此 [threading(“apartment”)] 和 [threading(apartment)] 都有效,因为编译器会添加缺失的引号。 在传递命名参数给属性时,始终会强制执行类型。 

客户变通方法

编译器会显示一个提示,表明您在这种情况下应该尝试使用带引号或不带引号版本的参数。 用户需要修复他们的代码。 

原理

我们通过添加参数检查验证来增强了我们的属性支持。 这样,我们的客户就不会因为属性构造函数的参数不正确而遇到意外行为。 

17. uuid 属性不再能定位托管类型

受影响的用户场景

使用 uuid 属性定位托管目标的旧语法代码。 

描述

我们在解析 uuid 属性到托管类型目标时,会将其映射到 BCL 的 GuidAttribute。 

客户变通方法

客户在定位托管类型时应使用 BCL 的 GuidAttribute 属性。 

原理

。NET Framework 提供了 UUID 属性供托管目标使用。 我们希望用户使用它,而不是在后台更改他们的代码。 

18. 将 CLI 数组传递给自定义属性的语法已更改

受影响的用户场景

使用接受构造函数中 CLI 数组的用户定义自定义属性的旧语法代码需要更新。 

描述

现在不再从聚合初始化列表中推导数组类型。 我们现在需要指定类型以及初始化列表。 

客户变通方法

客户需要显式指定数组类型以及聚合初始化,如下所示:[Attr(new String*[] {"a", "B"}] 

原理

编译器无法总是正确地从聚合初始化列表:{ 1, 'c', 1i64 } 推导出数组类型,这里的数组类型是什么? 因此,我们希望用户更加具体,并使用类似于我们在新语法中为 CLI 数组的聚合初始化添加的语法。 

19. 属性查找更改 - 编译器仅在属性块内查找非后缀属性名

受影响的用户场景

在属性块外部引用没有后缀的属性名称的代码将无法编译。 

描述

编译器仅在属性代码块内考虑没有其后缀的名称。 在其他地方,类名应遵循正常的 C++ 查找规则,并应使用其完整名称进行引用。 

客户变通方法

用户需要将 Attribute 后缀附加到其引用并遵循 C++ 查找规则来修复其代码。 

原理

我们这样做的目的是解决非属性托管类和属性托管类之间的查找歧义。 例如,如果存在 ref class A 和 ref class AAttribute : System::Attribute,那么 A ^a 是什么类型? 

20. 编译器不会在缺少类型的声明中注入默认类型 int

受影响的用户场景

代码中缺少声明类型的代码将不再默认为 int,而是会出错。 

描述

这是标准 C++。 编译器不应在任何声明(无论是函数还是变量)中假定默认类型为 int。 

客户变通方法

客户应修复其代码或禁用默认情况下视为错误的警告(C4430)。 

原理

标准 C++ 不支持默认 int。 我们还想进行此更改,因为用户很可能不打算返回 int 类型,而只是忘记编写所需的返回类型。 

21. 值类型不再发出默认构造函数 - 这可能导致类型初始化程序在不同时间点运行

受影响的用户场景

依赖类型初始化程序在其类型对象实例化之前运行的用户代码可能行为不正确。 

描述

在我们以前版本的编译器中,我们为值类型发出了默认构造函数,这导致类类型的初始化程序在创建该类类型的任何对象之前运行;它也会在引用任何静态数据之前运行。 现在,我们只保证后者,因为不再发出默认构造函数。 

客户变通方法

客户需要使其代码符合类型初始化程序保证运行的新规则,或显式强制其运行。 

原理

我们考虑了两个原因来不为值类型提供默认构造函数:CLR 不能保证它们始终调用它(从而导致代码在运行时出现不稳定或不一致)以及提高了性能。 

22. 在托管编译中,本机类型默认为 private

受影响的用户场景

依赖这些默认为 public 的用户。 

描述

编译器将假定未指定类访问修饰符的本机类型为 private。 在 7.0 中,它的行为相同。 7.1 编译器会假定 public,这导致了一些意外和负面影响。 

客户变通方法

如果用户想让本机类为 public,他们将不得不显式在类声明中添加 public 访问说明符。 

原理

我们有不同的原因来恢复托管编译中本机类型的 7.0 行为。 当导入程序集时,命名空间被无关的本机类型污染,这些类型被带入。 这导致了与其他不区分大小写的语言(如 VB.NET)的互操作性问题,从而产生了名称冲突。 我们认为在程序集中使用的本机类型应保留为实现细节,因此默认情况下设为 private。 如果用户希望相反的行为,他始终可以使用 public 访问类修饰符。  

23. 开关更改:/clr 开关现在将编译新的语法 C++ 代码,而不是 Managed Extensions for C++ 语法

受影响的用户场景

为 Managed Extensions for C++ 编写并使用 /clr 开关编译的代码。 

描述

我们更改了 /clr 开关的含义,使其现在默认指向我们的新 C++ 语法来定位 CLR,而不是上一版本编译器中提供的旧语法。 

客户变通方法

将 /clr 更改为 /clr:oldSyntax 以编译使用 Managed Extensions for C++ 编写的代码。 

原理

使用 C++ 定位 .NET 平台时,新语法是最佳选择。 我们希望用户默认能够从这种丰富的新体验中受益。 

24. 开关更改:C 编译单元不支持 /clr

受影响的用户场景

使用 .c 扩展名或命令行选项 /TC 或 /Tc 结合 /clr 系列中的任何开关编译文件的用户将看到错误。 

描述

C 语言不支持 CLR。 

客户变通方法

用户可以将文件扩展名更改为 .cpp|.cxx 或使用 /TP 或 /Tp 命令行选项进行编译。 

原理

我们提供 C++ 语言来定位 .NET 平台,而不是 C 语言,因此 C 语言不支持 CLR。 考虑到这一点,我们使该编译场景无效。

© . All rights reserved.