C++ move semantics

 

Move Semantics

The following simple class will be used to illustrate move semantics and object lifecycle. std::move creates rvalue reference

Float.h

#pragma once
class Float
{
public:
	Float(float f); //constructor
	Float(const Float& f); //copy constructor
	Float(Float&& f) noexcept; //move constructor
	Float& operator=(const Float& rhs); //copy assignment operator
	Float& operator=(Float&& rhs) noexcept; //move assignment operator
	~Float(); //destructor
	float getValue() const; //getter
	void setValue(float f); //setter
	friend void swap(Float& first, Float& second) noexcept ; //copy helper function
	void moveFrom(Float& src) noexcept; //move helper function. Note param is lvalue reference
	float *m_flt; //pointer to heap based float
};

Float.cpp

#include <iostream>
#include <utility>
#include "Float.h"
using namespace std;

Float::Float(float f)
{ 
	m_flt = new float(f);
	printf("Float(float f) 0x%x\n", this);
}

Float::~Float()
{
	printf("~Float() 0x%x\n", this);
	delete m_flt;
}

Float::Float(const Float& f)
{ 

	this->m_flt = new float(f.getValue());
	printf("Float(const Float& f) 0x%x\n", this);
}

float Float::getValue() const
{ 
	return *m_flt;
}

void Float::setValue(float f)
{
	*m_flt = f;
}

Float::Float(Float&& f) noexcept
{ 
	m_flt = f.m_flt;
	f.m_flt = nullptr;
	printf("Float(Float&& f) 0x%x\n", this);
}

Float& Float::operator=(const Float& rhs)
{ 
	if (this == &rhs)
		return *this;

	Float temp(rhs);
	swap(temp, *this);
	printf("Float::operator=(const Float& rhs) 0x%x\n", this);
	return *this;

}
void swap(Float& first, Float& second) noexcept
{ 
	using std::swap;
	swap(first.m_flt, second.m_flt);
}

Float& Float::operator=(Float&& rhs) noexcept
{ 
	moveFrom(rhs);
	printf("Float::operator=(Float&& rhs) 0x%x\n", this);
	return *this;
}

void Float::moveFrom(Float& src) noexcept
{ 
	m_flt = src.m_flt;
	src.m_flt = nullptr;
} 

The following is the code that makes use of the Float class:

Scenario 1

#include <iostream>
#include "Float.h"
using namespace std;

void DoIt(Float p) //H
{ 
    cout << "Entered DoIt" << endl;
    cout << "Doit: Float is " << p.getValue() << "\n";
}

Float add(Float a, Float b) //Float a is C, Float b is D
{
    auto x = std::move(a); //E
    auto y = std::move(b); //F
    return (x.getValue() + y.getValue()); //return(...) is G
}

int main()
{
    Float f(3.0); //A
    Float g(4.0); //B
    DoIt(add(f, g));
    

    return 0;
}

Output:

Float(float f) 0xac4ff7d0 //ctor A
Float(float f) 0xac4ff7c8 //ctor B
Float(const Float& f) 0xac4ff798 //copy ctor C
Float(const Float& f) 0xac4ff790 //copy ctor D
Float(Float&& f) 0xac4ff7b8 //move ctor E
Float(Float&& f) 0xac4ff7c0 //move ctor F
Float(float f) 0xac4ff7a0  //object G constructed
~Float() 0xac4ff7c0 //F destroyed at end of add
~Float() 0xac4ff7b8 //E destroyed  at end of add
~Float() 0xac4ff790 //D destroyed end of add
~Float() 0xac4ff798 //C destroyed, end of add
Entered DoIt 
Doit: Float is 7
~Float() 0xac4ff7a0 //object G destroyed
~Float() 0xac4ff7c8 //B destroyed
~Float() 0xac4ff7d0 //A destroyed

Scenario 2

Avoiding copies when calling add. Call std::move to create rvalue reference when calling add:

#include <iostream>
#include "Float.h"
using namespace std;

void DoIt(Float p) //Float p is F
{ 
    cout << "Entered DoIt" << endl;
    cout << "Doit: Float is " << p.getValue() << "\n";
}

Float add(Float a, Float b) //Float a is C, Float b is D
{
    return (a.getValue() + b.getValue()); //E
}

int main()
{
    Float f(3.0); //A
    Float g(4.0); //B
    DoIt(add(std::move(f), std::move(g)));
    

    return 0;
}

Output:

Float(float f) 0x858ff6f0 //ctor for A
Float(float f) 0x858ff6e8 //ctor for B
Float(Float&& f) 0x858ff6d0 //move ctor for C,
Float(Float&& f) 0x858ff6c8 //move ctor for D
Float(float f) 0x858ff6c0 //temporary for E
~Float() 0x858ff6c8 //destroy D at end of add
~Float() 0x858ff6d0 //destroy C at end of add
Entered DoIt
Doit: Float is 7
~Float() 0x858ff6c0 // destroy temporary for E
~Float() 0x858ff6e8  //destroy B
~Float() 0x858ff6f0 //destroy A

Scenario 3

Move assignment operator being used:

#include <iostream>
#include "Float.h"
using namespace std;

void DoIt(Float p)
{ 
    cout << "Entered DoIt" << endl;
    cout << "Doit: Float is " << p.getValue() << "\n";
}

Float add(Float a, Float b)
{
    return (a.getValue() + b.getValue());
}

int main()
{
    Float f(3.0);
    Float g(4.0);
    Float k(5.0);

    g = std::move(k);
    DoIt(add(std::move(f), std::move(g)));
    

    return 0;
}

Output:

Float(float f) 0x772ffa68
Float(float f) 0x772ffa58
Float(float f) 0x772ffa60
Float::operator=(Float&& rhs) 0x772ffa58 //g = std::move(k)
Float(Float&& f) 0x772ffa40
Float(Float&& f) 0x772ffa38
Float(float f) 0x772ffa30
~Float() 0x772ffa38
~Float() 0x772ffa40
Entered DoIt
Doit: Float is 8
~Float() 0x772ffa30
~Float() 0x772ffa60
~Float() 0x772ffa58
~Float() 0x772ffa68

Comments

Popular posts from this blog

QTreeView and QTableView dynamic changes

C++ strings and string_view