NRVO with and without default copy constructor - c++

I make a demo code like below:
#include <iostream>
using namespace std;
class Test {
public:
Test();
~Test();
Test(const Test&);
};
Test::Test(const Test& rhs) {
cout << "do copy constructor function" << endl;
}
Test::Test()
{
cout << "do constructor function" << endl;
}
Test::~Test() {
cout << "do deconstructor function" << endl;
}
Test fun1() {
Test tmp;
return tmp; // NRVO
}
int main() {
Test t1 = fun1();
return 0;
}
I run it under VS2017 release mode, and c++ standard is set to c++17.
The result is as expected, copy constructor has not been run.
But when I comment out the self-defined copy constructor and run it again:
#include <iostream>
using namespace std;
class Test {
public:
Test();
~Test();
};
Test::Test()
{
cout << "do constructor function" << endl;
}
Test::~Test() {
cout << "do deconstructor function" << endl;
}
Test fun1() {
Test tmp;
return tmp; // NRVO
}
int main() {
Test t1 = fun1();
return 0;
}
It turns out NRVO is invalid, because constructor runs once while deconstructor runs twice.
So why would that be?

Related

how to initialize a class object reference in C++ to simulate NRVO?

I meet a course programming problem, which asks me to initialize the A a using passing by reference (initialize the A a in the func). How can I call A's constructor by A's reference?
#include <iostream>
using namespace std;
class A
{
public:
int x;
A()
{
cout << "default constructor" << endl;
x = 1;
}
A(int x)
{
cout << "constructor with param = " << x << endl;
this->x = x;
}
~A() {
cout << "destructor" << endl;
}
void print() {
cout << x << endl;
}
};
void fun(A& a)
{
a.A::A(10); // error!
return;
}
int main()
{
A a;
fun(a);
a.print();
return EXIT_SUCCESS;
}
There is a background of this problem. The teacher want us to replicate the NRVO(named return value optimization) result.
#include <iostream>
using namespace std;
class A
{
public:
int x;
A()
{
cout << "default constructor" << endl;
x = 1;
}
A(int x)
{
cout << "constructor with param = " << x << endl;
this->x = x;
}
~A() {
cout << "destructor" << endl;
}
void print() {
cout << x << endl;
}
};
A fun() {
A a = A(10);
return a;
}
int main()
{
A a = fun();
return EXIT_SUCCESS;
}
default g++ compiler:
constructor with param = 10
destructor
if we close the NRVO:
g++ test.cpp -fno-elide-constructors
constructor with param = 10
destructor
destructor
destructor
destructor
The teacher want us to replicate the NRVO(named return value optimization) result by passing by reference.
The syntax a.A::A(10); is incorrect.
Constructor is used to create an object of a class, you cannot call it on an already existing object. Even a constructor cannot be explicitly called. It is implicitly called by the compiler.
From general-1.sentence-2:
Constructors do not have names.
Thus, you cannot call a constructor explicitly. The compiler will automatically call the constructor when an object of that class-type is created.
You can not, not like this.
A reference always points to an initialized object. So you already failed before you called the function. The "return" argument is already initialized. And you can't initialized an initialized value again, not legally.
You can cheat by calling
std::construct_at(&a, 10);
For it to really reflect NRVO you could have something like this:
void fun(A *a)
{
std::construct_at(a, 10);
}
union UninitializedA {
std::byte uninitialized[sizeof(A)];
A a;
};
int main()
{
UninitializedA u;
fun(&u.a);
u.a.print();
u.a.~A();
return EXIT_SUCCESS;
}

Capturing lambda in std::function results in extra copies

I am trying to write some code which allows me to call a function at some later time by storing the function call and its arguments in a lambda/std::function. Ideally, the arguments would only be copied ONCE (and moved oterhwise) but the smallest number of copies I can achieve seems to be 2.
//==============================================================================
// INCLUDES
//==============================================================================
#include <iostream>
#include <functional>
#include <memory>
//==============================================================================
// VARIABLES
//==============================================================================
static std::unique_ptr<std::function<void()>> queueFunction;
//==============================================================================
// CLASSES
//==============================================================================
class Test {
public:
Test(int a, int b = 20, int c = 30) : _a(a), _b(b), _c(c) {
std::cout << "Test: Constructor" << std::endl;
}
~Test() {
std::cout << "Test: Destructor" << std::endl;
}
Test(const Test& other) :
_a(other._a)
{
std::cout << "Test: Copy Constructor" << std::endl;
}
Test(Test&& other) :
_a(std::move(other._a))
{
std::cout << "Test: Move Constructor" << std::endl;
}
Test& operator=(const Test& other) {
if (this != &other) {
_a = other._a;
std::cout << "Test: Assignment Operator" << std::endl;
}
return *this;
}
Test& operator=(Test&& other) {
if (this != &other) {
_a = std::move(other._a);
std::cout << "Test: Move Assignment Operator" << std::endl;
}
return *this;
}
friend std::ostream& operator<<(std::ostream& os, const Test& v) {
os << "{a=" << v._a << "}";
return os;
}
private:
int _a;
int _b;
int _c;
};
//==============================================================================
// FUNCTIONS
//==============================================================================
void foo(const Test& t);
void _foo(const Test& t);
template <typename F>
void queue(F&& fn) {
std::cout << "queue()" << std::endl;
queueFunction = std::make_unique<std::function<void()>>(std::forward<F>(fn));
}
void dequeue() {
std::cout << "dequeue()" << std::endl;
if (queueFunction) {
(*queueFunction)();
}
queueFunction.reset();
}
void foo(const Test& t) {
std::cout << "foo()" << std::endl;
queue([t](){
_foo(t);
});
//Only a single copy of Test is made here
/*
[t](){
_foo(t);
}();
*/
}
void _foo(const Test& t) {
std::cout << "_foo()" << std::endl;
std::cout << "t=" << t << std::endl;
}
//==============================================================================
// MAIN
//==============================================================================
int main() {
std::cout << "main()" << std::endl;
Test test1(20);
foo(test1);
dequeue();
std::cout << "main() return" << std::endl;
return 0;
}
The output of the above code is:
main()
Test: Constructor
foo()
Test: Copy Constructor
queue()
Test: Copy Constructor
Test: Copy Constructor
Test: Destructor
Test: Destructor
dequeue()
_foo()
t={a=20}
Test: Destructor
main() return
Test: Destructor
Which makes no sense to me. Shouldn't the lambda capture the instance of Test once, then forward that lambda all the way to the new std::function thus causing a move?
If I modify my queue function as such I can at least get rid of once copy.
void queue(std::function<void()> fn) {
std::cout << "queue()" << std::endl;
queueFunction = std::make_unique<std::function<void()>>(std::move(fn));
}
Output:
main()
Test: Constructor
foo()
Test: Copy Constructor
Test: Copy Constructor
queue()
Test: Destructor
dequeue()
_foo()
t={a=20}
Test: Destructor
main() return
Test: Destructor
But I still cannot understand where the extra copy is coming from.
Can someone help to enlighten me?
AFAICT the problem is the const of the foo() argument. When you capture t inside foo(const Test& t), then the type of that capture inside the lambda is also const. Later when you forward the lambda, the lambda's move constructor will have no choice but copy, not move, the capture. You cannot move from const.
After changing foo to foo(Test& t) I get:
main()
Test: Constructor
foo()
Test: Copy Constructor
queue()
Test: Move Constructor
Test: Move Constructor
Test: Destructor
Test: Destructor
dequeue()
_foo()
t={a=20}
Test: Destructor
main() return
Test: Destructor
Alternative solution, mentioned in https://stackoverflow.com/a/31485150/85696, is to use capture in the form [t=t].
With move-capture and two other changes it is possible to eliminate this remaining copy constructor too:
- void foo(const Test& t) {
+ void foo(Test t) {
...
- queue([t](){
+ queue([t = std::move(t)](){
...
- foo(test1);
+ foo(std::move(test1));
main()
Test: Constructor
Test: Move Constructor
foo()
Test: Move Constructor
queue()
Test: Move Constructor
Test: Move Constructor
Test: Destructor
Test: Destructor
Test: Destructor
dequeue()
_foo()
t={a=20}
Test: Destructor
main() return
Test: Destructor

Parenthesis vs curly braces

#include<iostream>
using namespace std;
class test
{
public:
int a,b;
test()
{
cout<<"default construictor";
}
test(int x,int y):a(x),b(y){
cout<<"parmetrized constructor";
}
};
int main()
{
test t;
cout<<t.a;
//t=(2,3);->gives error
t={2,3}; //calls paramterized constructor
cout<<t.a;
}
Output:- default construictor4196576parmetrized constructor2
why in case of the above example, parameterized constructor(even though default constructor is already called.) is called in case of {} and not in ()
I added some additional code to show what is actually happening.
#include<iostream>
using namespace std;
class test
{
public:
int a,b;
test()
{
cout << "default constructor" << endl;
}
~test()
{
cout << "destructor" << endl;
}
test(int x,int y):a(x),b(y)
{
cout << "parameterized constructor" << endl;
}
test& operator=(const test& rhs)
{
a = rhs.a;
b = rhs.b;
cout << "assignment operator" << endl;
return *this;
}
};
int main()
{
test t;
cout << t.a << endl;
//t=(2,3);->gives error
t={2,3}; //calls parameterized constructor
cout << t.a << endl;
}
Output:
default constructor
4197760
parameterized constructor
assignment operator
destructor
2
destructor
So the statement t={2,3}; is actually constructing a new test object using the parameterized constructor, calling the assignment operator to set t to be equal to the new, temporary test object, and then destroying the temporary test object. It's equivalent to the statement t=test(2,3).
use
test t(2,3);
instead of
test t;
t=(2,3);
Because parentheses are used after object declaration.

Return class destructor

I'm a student and I'm learning C++. I quite good at C++ still "simple" things get me entangled. I've recently learn classes, methods, constructor/deconstructor, inheritance, virtual, etc. I have this code :
#include <iostream>
using namespace std;
class test {
int a, c;
public:
test() { cout << "Constructor\n"; }
test(int a) :a(a) { cout<<"Explicit Constructor\n"; }
test foo(const test&, const test&);
~test() { cout << "DECONSTRUCTOR!\n"; }
};
test test::foo(const test &t1, const test &t2) {
test rez;
rez.c = t1.a + t2.a;
return rez;
}
void main() {
test t1(5), t2(21), rez;
rez.foo(t1, t2);
cin.ignore();
}
I know that in foo I create a local class that is deleted when out of scope. So when foo is called I should see one constructor and one destructor, but it gives me one more deconstructor, so I have one constructor for two destructors.
Add one more method to your class test:
test(test const &) { cout << "Copy constructor" << endl; }
See what happens then.
You've forgotten to implement the copy constructor:
test(const test &rhs)
{
cout << "Constructor";
}
It's being used to copy the rez object back to the caller of foo
Refer to comments:
test test::foo(const test &t1, const test &t2) {
test rez;
// constructor for rez invoked
rez.c = t1.a + t2.a;
return rez;
// destructor for rez invoked
}
void main() {
test t1(5), t2(21), rez;
// 3 constructor invocations
rez.foo(t1, t2);
// constructor for return value is called
// but you are missing it since you did not implement copy constructor
// destructor for return value is called
// as you did not use it
cin.ignore();
// 3 destructor invocations
}

How do I invoke the move constructor?

In the code show below, how do I assign rvalue to an object A in function main?
#include <iostream>
using namespace std;
class A
{
public:
int* x;
A(int arg) : x(new int(arg)) { cout << "ctor" << endl;}
A(const A& RVal) {
x = new int(*RVal.x);
cout << "copy ctor" << endl;
}
A(A&& RVal) {
this->x = new int(*RVal.x);
cout << "move ctor" << endl;
}
~A()
{
delete x;
}
};
int main()
{
A a(8);
A b = a;
A&& c = A(4); // it does not call move ctor? why?
cin.ignore();
return 0;
}
Thanks.
Any named instance is l-value.
Examples of code with move constructor:
void foo(A&& value)
{
A b(std::move(value)); //move ctr
}
int main()
{
A c(5); // ctor
A cc(std::move(c)); // move ctor
foo(A(4));
}