Dav*_*vid 5 c++ visual-studio c++17
看下面的代码示例,我希望它可以作为返回值优化(RVO)的一部分执行强制复制省略并使用C ++ 17(/ std:c ++ 17)进行编译,但是在Visual Studio 2017上编译时会出错(我正在使用VS17,更具体地说是15.9.8)。
class NoCopyOrMove
{
public:
NoCopyOrMove() = default;
NoCopyOrMove(int a, int b){}
NoCopyOrMove(const NoCopyOrMove&) = delete;
NoCopyOrMove& operator=(const NoCopyOrMove&) = delete;
NoCopyOrMove(NoCopyOrMove&&) = delete;
NoCopyOrMove& operator=(NoCopyOrMove&&) = delete;
private:
int a, b;
};
NoCopyOrMove get(bool b)
{
return b ? NoCopyOrMove(1,2) : NoCopyOrMove();
//if (b)
// return NoCopyOrMove(1, 2);
//return NoCopyOrMove();
}
int main()
{
NoCopyOrMove m = get(true);
}
Run Code Online (Sandbox Code Playgroud)
错误是:
error C2280: 'NoCopyOrMove::NoCopyOrMove(NoCopyOrMove &&)': attempting to reference a deleted function
note: see declaration of 'NoCopyOrMove::NoCopyOrMove'
note: 'NoCopyOrMove::NoCopyOrMove(NoCopyOrMove &&)': function was explicitly deleted
Run Code Online (Sandbox Code Playgroud)
注意:似乎可以在GCC上编译,并且带if / else的版本可以在两个版本上正常编译,所以不确定我缺少什么。
我发现了一些其他有关stackoverflow的问题,但它们来自c17之前的时代,主要指“调用复制而不是移动”,因此再次提出了疑问。
基于cppreference的复制省略发生:
在return语句中,当操作数是与函数返回类型相同的类类型(忽略cv限定)的prvalue时:
三元运算符的结果应为prvalue:
一种 ?b:c,某些b和c的三元条件表达式(有关详细信息,请参见定义);
有什么想法为什么不能编译?
编辑以使用更简单的代码:
给定上面的NoCopyOrMove,下面的代码也试图调用move-ctor。
int main()
{
volatile bool b = true;
NoCopyOrMove m = b ? NoCopyOrMove(1,2) : NoCopyOrMove();
}
Run Code Online (Sandbox Code Playgroud)
更新:报告链接
Yes. This is a bug in MSVC. Pretty much every other compiler that supports C++17 compiles it. Below we have the assembly produced by:
And all of them compile it with -std=c++17 or -std=c++1z (for ellcc).
Conditional expressions (the ones formed by the ternary operator) produce values according to these rules (see section 8.5.16).
Paragraph 1 of 8.5.16 describes sequencing, and parts 2 through 7 describe the value category of the resulting expression (see section 8.2.1 for a description of value categories).
Otherwise, the result is a prvalue. If the second and third operands do not have the same type, and either has (possibly cv-qualified) class type, overload resolution is used to determine the conversions (if any) to be applied to the operands (16.3.1.2, 16.6). If the overload resolution fails, the program is ill-formed. Otherwise, the conversions thus determined are applied, and the converted operands are used in place of the original operands for the remainder of this subclause.
This gives us our answer. The result is a prvalue, so it's unnecessary to use the copy or move constructors, as the value will be instantiated in the memory provided by the calling function (this memory location is passed as a "hidden" parameter to your function).
Jon Harper was kind enough to point out that the standard states:
A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed. (11.4.3.2)
This begs the question: does your program implicitly refer to the move constructor or the copy constructor?
The answer to this is no. Because the result of the conditional expression is a prvalue, no temporary is materialized, and as a result neither the move constructor or the copy constructor is referenced, either explicitly or implicitly. To quote cppreference (emphasis mine):
Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible, as the language rules ensure that no copy/move operation takes place, even conceptually:
In a return statement, when the operand is a prvalue of the same class type (ignoring cv-qualification) as the function return type:
T f() { return T(); }
f(); // only one call to default constructor of T- In the initialization of a variable, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the variable type:
T x = T(T(f())); // only one call to default constructor of T, to initialize x
One source of contention is whether or not Copy Elision is guaranteed. It is important to distinguish between Named Return Value Optimization, and pure Return Value Optimization.
If you return a local variable, it's not guaranteed. This is Named Return Value Optimization. If your return statement is an expression that's a prvalue, it is guaranteed.
For example:
NoCopyOrMove foo() {
NoCopyOrMove myVar{}; //Initialize
return myVar; //Error: Move constructor deleted
}
Run Code Online (Sandbox Code Playgroud)
I am returning a an expression (myVar) that is the name of an object of automatic storage. In this case, return value optimization is permitted but not guaranteed. Section 15.8.3 of the standard applies here.
On the other hand, if I write:
NoCopyOrMove foo() {
return NoCopyOrMove(); // No error (C++17 and above)
}
Run Code Online (Sandbox Code Playgroud)
Copy Elision is guaranteed, and no copy or move takes place. Similarly, if I write:
NoCopyOrMove foo(); //declare foo
NoCopyOrMove bar() {
return foo(); //Returns what foo returns
}
Run Code Online (Sandbox Code Playgroud)
Copy Elision is still guaranteed because the result of foo() is a prvalue.
MSVC does, in fact, have a bug.
| 归档时间: |
|
| 查看次数: |
188 次 |
| 最近记录: |