复制文件提示参数不正确 (复制文件提示参数不正确怎么办? 蝎子

为了发帖,标题中的复制/移动省略是 Copy/Move Elision 硬翻译,请大海涵。我将在下面同时使用这两个术语。

Visual Studio 中 Copy/Move Elision 的变化

在 Visual Studio 2022 版本 17.4 预览版 3 我们显著增加了适用性Copy/Move Elision 使用户能够更好地控制这些转换的数量。

Copy/Move Elision 是什么?

当 C 函数中的 return 当关键字跟随非内置表达式时,执行该关键字 return 表达式结果将复制到调用函数的返回槽(Return Slot)中。因此,将调用非内置复制或移动结构函数。然后,作为退出函数的一部分,可能包括调用函数局部变量的分析函数 return 在关键字后面的表达式中命名的任何变量。

C 该规范允许编译器直接在调用函数的返回槽中构建返回对象,以省略复制或移动构造函数作为返回的一部分。与大多数其他优化不同,这种转换允许对程序输出产生可观察的影响 – 即复制或移动结构函数和相关分析函数可少调用一次。

Visual Studio 中的 Copy/Move Elision

C 标准要求将返回值初始化为 return 句子的一部分(例如,当返回类型为时 Foo 函数返回 Foo()编译器需要执行 Copy/Move Elision。
Microsoft Visual C 编译器总是根据需要执行返回语句 Copy/Move Elision,无论传递给编译器的标志如何。这种行为保持不变。

在 Visual Studio 17.4 预览版 3 中对可选 Copy/Move Elision 的更改

当返回值为命名变量时,编译器可以省略复制或移动,但不必要。C 即使编译器在所有情况下都省略了构造函数,该标准仍然需要定义复制或移动构造函数来命名返回变量。
在 Visual Studio 2022 版本 17.4 预览版 3 以前,禁用优化(如使用) /Od 或使用编译器标志 #pragma optimize(“”,off))当编译器只执行强制执行时,Copy/Move Elision。使用 /O2 通过简单的控制流为优化函数,编译器将执行可选的标志Copy/Move Elision。

从 Visual Studio 2022 版本 17.4 预览版 3 一开始,我们为开发人员提供了新的服务 /Zc:nrvo 保持编译器标志一致的选项。默认情况下,使用时 /O2 标志、/permissive- 编译代码或编译代码时 /std:c 20 编译或更高版本时,将传输 /Zc:nrvo 标志。复制和移动省略将尽可能通过此标志执行。我们希望默认使用未来版本 /Zc:nrvo。此外,开发者也可以使用它 /Zc:nrvo- 可选标志显式禁用Copy/Move Elision。请注意,强制型不能禁用。Copy/Move Elision。

在 Visual Studio 2022 版本 17.4 预览版 3 中,当使用 /Zc:nrvo、/O2、/permissive-或 /std:c 20 当可选复制/移动省略启用更高版本的标志时,我们也添加了它Copy/Move Elision的位置。

可选 Copy/Move Elision 的示例

可选 Copy/Move Elision 最简单的例子是以下函数:
Foo SimpleReturn(){
Foo result;
return result;
}

在这种情况下,如果传递了 /O2 早期版本的标志 MSVC 将结果复制或移动到返回槽中的编译器。在 Visual Studio 2022 版本 17.4 预览版 3 中间,如果传递 /permissive-、/std:c 20 或更高的版本或 /Zc:nrvo 如果标志被传输,复制或移动也将被省略 /Zc:nrvo- 复制或移动标志。

从 Visual Studio 2022 版本 17.4 预览版 3 开始,如果将 /O2、/permissive-、/std:c 20 或更高的版本或 /Zc:nrvo 标志传递给编译器, /Zc:nrvo- 标志没有传输到编译器,我们现在在以下其他情况下执行复制/移动省略。

回到循环中

Foo ReturnInALoop(int iterations){
for (int i = 0; i < iterations; i){
Foo result;
if (i == (iterations / 2)){
return result;
}
}
}
结果对象在每次迭代开始时都会正确构建,并在每次迭代结束时销毁。其析构函数不会在返回结果的迭代中调用。在返回结果的迭代中,退出函数时不会调用其分析函数。当返回对象超过函数范围时,函数的调用器将销毁对象。

返回异常处理

Foo ReturnInTryCatch(){
try{
Foo result;
return result;
}catch (…){}
}
如果传递了 /O2、/permissive-、/std:c 20 或更高版本,或者传递了 /Zc:nrvo 标志,而 /Zc:nrvo- 如果标志未传输,目前将省略结果对象的复制或移动。我们仍然可以妥善处理更复杂的情况,例如:

int n;

void throwFirstThreeIterations(){
n;
if (n <= 3) throw n;
}

Foo ComplexTryCatch()
{
Label1:
Foo result;

try{
throwFirstThreeIterations();
return result;
}
catch(…){
goto Label1;
}
}

对象将在调用方函数的返回槽中结构,并且在成功返回时不会调用复制/移动构造函数或分析函数。当出现异常时,分析结果的对象是否取决于向编译器传递哪些异常处理标志。当发生异常时,分析结果的对象是否取决于向编译器传递哪些异常处理标志。默认情况下,不会发生堆栈,因此不会调用分析函数。但是,如果使用 /EHs、/EHa 或 /EHr 标志使用堆栈进行异常处理, goto Label1 因为它跳转到初始化结果之前,会导致调用结果的析构函数。无论如何,当再次到达表达式时 Foo 对象将在返回槽中重新构建。

复制具有默认参数的构造函数

现在,我们可以正确检测到具有默认参数的复制或移动构造函数仍然是复制或移动构造函数,因此在上述情况下可以省略。复制构造函数具有默认参数如下:
struct StructWithCopyConstructorDefaultParam{
int X;

StructWithCopyConstructorDefaultParam(int x) : X(x){}
StructWithCopyConstructorDefaultParam(StructWithCopyConstructorDefaultParam const& original, int defaultParam = 0) :
X(original.X defaultParam){
printf(“Copy constructor called.\ ”);
}
};

对NRVO的限制

尽管 MSVC 编译器现在在更多的情况下执行Copy/Move Elision,但并不总能执行它
。若要知道为什么会这样,请考虑以下函数:
Foo WhichShouldIReturn(bool condition){
Foo resultA;
if (condition){
Foo resultB;
return resultB;
}
return resultA;
}

在返回槽中返回复制省略结构的对象,但在这种情况下,返回槽中应构建哪个对象?为了在返回结果A时省略结果A的副本,必须在返回槽中构建。为了在返回结果A时省略结果A的副本,必须在返回槽中构建。但是,如果条件是真实的,则需要销毁结果 A 在返回槽中结构结果之前 B。两条路径无法复制省略。

目前,我们选择避免在函数中的所有路径上执行可选的Copy/Move Elision,在任何路径上都不可能。然而,可能会改变内联决策、消除死代码和其他优化Copy/Move Elision的可能性。因此,写作依赖于命名变量Copy/Move Elision除非使用某些行为不安全的,除非使用 /Zc:nrvo- 禁止所有可选的Copy/Move Elision。

只要堆栈开栈进行异常处理或不引起异常,仍然可以安全地假设每个结构函数都有匹配的分析函数。

总结

写旧时代 C ,一直担心如何高性能地返回一个对象。
是的,就在下面。

最后

Microsoft Visual C 团队博客是我最喜欢的博客之一,有很多关于Visual C 知识和最新发展进展。如果你对的话,大浪淘沙Visual C 如果这种古老的技术仍然如此感兴趣,你可以经常去他们那里(或我)。
本文来自:《Improving Copy and Move Elision》


电脑知识