Regarding the flow when returning class variables

Asked 1 years ago, Updated 1 years ago, 316 views

I would like to ask you about the flow of processing when returning class variables in c++.

For example,

class Hoge {
// Class Implementation
};

Hoge func(){
  Hoge fuga;

  // Creating a fuga

  return std::move(fuga);
}

int main() {
  Hoge foo=func();

  return 0;
}

If so, the flow of processing at the end of func is

Is that correct?
My question is

Thank you for your cooperation.

[Additional note]
The C++ version to be used is assumed to be C++14, but
If the behavior changes with C++17 or 20,
I would appreciate it if you could let me know including that.

c++

2022-11-25 16:56

4 Answers

Presentation code is a typical example of NRVO not being able to do it

#include<utility>
class Hoge {};
Hoge func1(){
    Hogeh;
    return;
}
Hoge func2(){
    Hogeh;
    return std::move(h);
}

For the code, g++/clang++ is

$g++-std=c++14-W-Wall-pedantic-O-g-cnrvo.cpp 
nrvo.cpp: In function 'Hoge func2()':
nrvo.cpp: 9:21:warning:moving a local object in a return statement events copy elision [-Wpessimizing-move]
    9 | return std::move(h);
      |            ~~~~~~~~~^~~
nrvo.cpp:9:21:note:remove'std::move'call
$ clang++-std=c++14-W-Wall-pedantic-O-g-cnrvo.cpp 
nrvo.cpp: 9:12: warning: moving a local object in a return statement events copy elision [-Wpessimizing-move]
    return std::move(h);
           ^
nrvo.cpp:9:12:note:remove std::move call here
    return std::move(h);
           ^~~~~~~~~~ ~
1 warning generated.
$

In this example, it seems that it is better not to write std::move because they complain as shown in .

$g++--version
g++ (GCC) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ clang++--version
clang version 8.0.1 (tags/RELEASE_801/final)
Target: x86_64-unknown-windows-cygnus
Thread model: posix
InstalledDir: /usr/bin
$ uname-a
CYGWIN_NT-10.0-19044 mypc 3.3.6-341.x86_64 2022-09-05 11:15 UTC x86_64 Cygwin
$

Incidentally, the warning was also displayed in -std=c++17.

RVO/NRVO was implemented in most compilers since C++98 (although not required by language standards), so you should write without std::move considering backward compatibility.


2022-11-25 17:37

  • fuga is the left side value, so I think I need to explicitly std::move to move construct the return value of func from fuga. Is this correct?
  • If the return value of the function initializes the variable, will the move construct be performed or will the constructor be omitted, such as NRVO?

Wrong

The result of calling a function whose return type is not a reference is prvalue, regardless of whether the expression given in the return expression is right or left.

prvalue is considered a type of right-hand side value and moves occur if it appears as the initializer of foo, but the rule prvalue allows you to omit copying (must be omitted after C++17) applies, and it does not copy or move. It is considered to be the same as directly initialized

If you use std::move for the expression given to return, you will force a move on the temporary object generation. If you force a move where a copy is interpreted as an optional copy, the move is ensured.

In a simple interpretation, there are steps such as return expression given to return → temporary object returned by variable (initiator of variable) → value passed to constructor, so it can be omitted with various conditions. In the example of this question, std::move with the return expression, but the return value of the function allows you to omit both copies and moves when initializing variables.


2022-11-25 19:12

The flow of processing at the end of func is

Is that correct?

Both are partially correct.Strict behavior is as follows:

  • In this case, the std::move function is not utilized; that is, return fuga; is preferred.At worst, implicit move construction is guaranteed.
  • Depending on the optimization of the C++ compiler, a Named Return Value Optimization (NRVO) may be performed and the move constructor call may be omitted.
  • C++11 through C++14: Move constructs at worst (not copy constructs), and optimization of the C++ compiler may result in Return Value Optimization (RVO) and skip the Move constructor call.
  • C++17 or later: RVO (Return Value Optimization) is always guaranteed.

Since fuga is the left side value, I think it is necessary to explicitly std::move to construct the return value of func from fuga, but is this correct?

No.

If you specify the name (left side value) of the local variable in the return statement, the move construct is attempted first by rereading it as if it were the right side value.If this attempt results in a compilation error, the copy construct will be attempted again from the original left side value.

If the return value of the function initializes the variable, will the move construct be performed or will the constructor be omitted in NRVO etc.?

The answer is as above.The optimization here is not NRVO, but RVO.


2022-11-25 20:25

It didn't come out when I asked the respondent, so I looked it up as far as I could easily (I can't even ask questions separately).
Leave a brief verification result

#include<iostream>
using namespace std;
structure Hoge {
    Hoge() {cout<<"Hoge()"<<endl;}
    Hoge(const Hoge&org) {cout<<"Hoge(const Hoge&)"<endl;}
    Hoge&operator=(const Hoge&org) {cout<"operator=(const Hoge&)"<endl;return*this;}
    ~Hoge() {cout<"~Hoge()"<endl;}
# if (__cplusplusplus>=201103L)
    Hoge(Hoge&org) {cout<<"Hoge(Hoge&&&endl;}
    Hoge&operator=(Hoge&org) {cout<"operator=(Hoge&&)"<endl;return*this;}
#endif
};
Hoge func(){
  Hoge fuga;
  return fuga;
}
int main() {
  Hoge foo1 = func();
  Hoge foo2 = foo1;
  foo2 = foo1;
# if (__cplusplusplus>=201103L)
  Hoge foo3 = std::move(foo1);
  foo3 = std::move(foo2);
#endif
  return 0;
}

I tried to do this on the following site:
https://godbolt.org/
The following processing systems are covered:
clang x86-643.3, 3.4.1, 3.5.2, 3.6, 3.7, 3.8.1, 3.9.1, 4.0.1, 5.0.2, 6.0.1, 7.0.1, 7.1.0, 8.0.1, 10.0.1, 11.0.1, 12.0.1, 13.0.1, 14.0.0, 15.0.> 3.3, 3.4.1, 3.5.2, 3.6, 3.7, 3.8.1, 3.9.1, 4.0.1, 5.0.2, 6.0.1, 7.0.1, 7.0.1, 8.0.1, 10.0.1, 11.0.1, 12.0.1, 13.0.1, 14.0.0, 15.0.0
gcc x86-64 4.7.4, 4.8.5, 4.9.3, 5.5, 6.4, 7.5, 8.5, 9, 5, 10.4, 11.3, 12.2
msvc x64 19.33 (this is the only way to do it)
(If only the last version is different, it has not been implemented except for the largest version.)

Check Item 1
- Wall-std = c++98 and the output should be as follows:(msvc not implemented)

Hoge()
Hoge (const Hoge &)
operator=(const Hoge&)
~Hoge()
~Hoge()

Check Item 2
- Wall-std = c++ XX (where XX is the maximum value that can be compiled in 11, 14, 17, 2a, 20, 2b, 23) with the following output:

Hoge()
Hoge (const Hoge &)
operator=(const Hoge&)
Hoge (Hoge & & )
operator= (Hoge & & )
~Hoge()
~Hoge()
~Hoge()

(msvc is done at /std:c++20/Zc:__cplusplus/O2, remove /O2 and turn off optimization to get a copy.)
*Visual confirmation (I didn't see the assembler).It would be nice to have an online environment where you can do this kind of test.

Results
As for clang/gcc, I was able to confirm the omission of copy without optimization in all the versions listed.msvc only confirmed during optimization.

Additional
The output when msvc did not optimize was move constructor.

Hoge()
Hoge (Hoge & & )
~Hoge()
Hoge (const Hoge &)
operator=(const Hoge&)
Hoge (Hoge & & )
operator= (Hoge & & )
~Hoge()
~Hoge()
~Hoge()


2022-11-25 22:49

If you have any answers or tips


© 2024 OneMinuteCode. All rights reserved.