NRVO. 关闭省略。C++11与C++17

huangapple go评论58阅读模式
英文:

NRVO. Turning off elision. C++11 vs C++17

问题

我正在研究复制省略(copy elision)以及返回值优化(RVO/NRVO)。当我使用 g++ 编译时,使用 -fno-elide-constructors 标志时,我发现对于 C++11 和 C++17 会有不同的行为。

我了解到,C++17 在特定情况下要求进行 RVO。我本以为下面的代码符合 NRVO(除非编译器首先也消除了命名变量 y?)

struct rvo 
{       
        rvo () : val {0} { std::cout << "Default Constructor" << std::endl;};
        ~rvo (){ std::cout << "Destructor" << std::endl;        };
        rvo(const rvo& in){ std::cout << "Copy Constructor" << std::endl; }
        rvo(rvo&& in){ std::cout << "Move Constructor" << std::endl; }
        
        double val;
};

rvo f1(rvo& x){ 
        rvo y {x};
        y.val++;
        return y;
}

int main()
{
        rvo A{};
        rvo B {f1(A)};
        return 0;
}

没有额外标志编译时的输出如下:

Default Constructor
Copy Constructor
Destructor
Destructor

当使用 -fno-elide-constructors--std=c++11 编译时,输出如下:

Default Constructor
Copy Constructor
Move Constructor
Destructor
Move Constructor
Destructor
Destructor
Destructor

这是可以理解的,因为临时变量被创建。

我的问题是,当我使用 -fno-elide-constructors--std=c++17 编译时,它消除了一个移动操作,输出如下:

Default Constructor
Copy Constructor
Move Constructor
Destructor
Destructor
Destructor

看起来它消除了临时返回变量,并将从函数变量 y 移回到 B 主函数中。即使我已关闭复制省略。它是将其视为强制执行的 RVO,还是我对某些基本概念有误解?

我找到了这个示例,https://stackoverflow.com/questions/61743861/rvo-nrvo-can-not-be-disabled-in-c17-in-clang,但那个示例显然是 RVO,而我认为我的示例是 NRVO,因此不是强制执行的。

如果有人可以确认,非常感谢。

英文:

I am looking at copy elision and RVO/NRVO. When I run g++ with -fno-elide-constructors I see different behaviour for c++11 and c++17.

I understand that c++17 mandates RVO under certain circumstances. I would have assumed that the below counts as NRVO (unless the compiler first eliminates the named variable y too?)

struct rvo 
{       
        rvo () : val {0} { std::cout << "Default Constructor" << std::endl;};
        ~rvo (){ std::cout << "Destructor" << std::endl;        };
        rvo(const rvo& in){ std::cout << "Copy Constructor" << std::endl; }
        rvo(rvo&& in){ std::cout << "Move Constructor" << std::endl; }
        
        double val;
};

rvo f1(rvo& x){ 
        rvo y {x};
        y.val++;
        return y;
}

int main()
{
        rvo A{};
        rvo B {f1(A)};
        return 0;
}

Output when compiled up with no additional flags is as expected

Default Constructor
Copy Constructor
Destructor
Destructor

When compiled up with -fno-elide-constructors and --std=c++11 the output is

Default Constructor
Copy Constructor
Move Constructor
Destructor
Move Constructor
Destructor
Destructor
Destructor

And this is fine as the temporary is being created.

My question is that when I compile up with -fno-elide-constructors and --std=c++17, it eliminates one of the moves.

Default Constructor
Copy Constructor
Move Constructor
Destructor
Destructor
Destructor

It appears that it is eliminating the temporary return variable, and doing a move from the function variable y back to B in main. Even though I have switched off elision. Is it considering it as mandatory RVO or am I misunderstanding something basic?

I found this example, https://stackoverflow.com/questions/61743861/rvo-nrvo-can-not-be-disabled-in-c17-in-clang but that one is obvious RVO whereas I thought my example was NRVO and thus not mandatory.

Thanks very much if anyone can confirm.

答案1

得分: 2

这是因为从[tag:C++17]开始有强制复制省略,而是直接从返回值f(A)创建对象B,因此没有使用移动构造函数或复制构造函数,因此输出如下。

这可以从复制省略文档中看出:

> 在以下情况下,编译器需要省略类对象的复制和移动构造,即使复制/移动构造函数和析构函数具有可观察的副作用。对象直接构造在它们原本应该被复制/移动到的存储中。 复制/移动构造函数不一定要存在或可访问:
>
> * 在对象初始化时,当初始化表达式是相同类类型的纯右值(忽略cv资格限制)时
> c++ > T x = T(T(f())); // 只调用一次T的默认构造函数,以初始化x >

英文:

This is because from [tag:C++17] there is mandatory copy elision and instead the object B is created directly from the return value f(A) so that there is no use of a move constructor or a copy ctor and hence the output.

This can be seen from copy elision documentation:

> 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:
>
> * In the initialization of an object, 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
>

huangapple
  • 本文由 发表于 2023年2月23日 22:31:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/75546216.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定