I am having problems in move semantics of C++11. I am using gcc 4.9.2 20150304 (prerelease) with the -std=c++11 switch, but I am having problems in move constructor's not being invoked.
I have the following source files:
- densematrix.h
#ifndef DENSEMATRIX_H_
#define DENSEMATRIX_H_
#include <cstddef>
#include <iostream>
#include <utility>
class DenseMatrix {
private:
size_t m_ = 0, n_ = 0;
double *values = nullptr;
public:
/* ctor */
DenseMatrix( size_t, size_t );
/* copy ctor */
DenseMatrix( const DenseMatrix& rhs );
/* move ctor */
DenseMatrix( DenseMatrix&& rhs ) noexcept;
/* copy assignment */
const DenseMatrix& operator=( const DenseMatrix& rhs );
/* move assignment */
const DenseMatrix& operator=( DenseMatrix&& rhs ) noexcept;
/* matrix multiplication */
DenseMatrix operator*( const DenseMatrix& rhs ) const;
/* dtor */
~DenseMatrix();
};
#endif
- densematrix.cpp
#include "densematrix.h"
/* ctor */
DenseMatrix::DenseMatrix( size_t m, size_t n ) :
m_( m ), n_( n ) {
std::cout << "ctor with two arguments called." << std::endl;
if ( m_*n_ > 0 )
values = new double[ m_*n_ ];
}
/* copy ctor */
DenseMatrix::DenseMatrix( const DenseMatrix& rhs ) :
m_( rhs.m_ ), n_( rhs.n_ ) {
std::cout << "copy ctor called." << std::endl;
if ( m_*n_ > 0 ) {
values = new double[ m_*n_ ];
std::copy( rhs.values, rhs.values + m_*n_, values);
}
}
/* move ctor */
DenseMatrix::DenseMatrix( DenseMatrix&& rhs ) noexcept :
m_( rhs.m_ ), n_( rhs.n_ ), values( rhs.values ) {
std::cout << "move ctor called." << std::endl;
rhs.values = nullptr;
}
/* copy assignment */
const DenseMatrix& DenseMatrix::operator=( const DenseMatrix& rhs ) {
std::cout << "copy assignment called." << std::endl;
if ( this != &rhs ) {
if ( m_*n_ != rhs.m_*rhs.n_ ) {
delete[] values;
values = new double[ rhs.m_*rhs.n_ ];
}
m_ = rhs.m_;
n_ = rhs.n_;
std::copy( rhs.values, rhs.values + m_*n_, values);
}
return *this;
}
/* move assignment */
const DenseMatrix& DenseMatrix::operator=( DenseMatrix&& rhs ) noexcept {
std::cout << "move assignment called." << std::endl;
m_ = rhs.m_;
n_ = rhs.n_;
delete[] values;
values = rhs.values;
rhs.values = nullptr;
return *this;
}
/* matrix multiplication */
DenseMatrix DenseMatrix::operator*( const DenseMatrix& rhs ) const {
return DenseMatrix( this->m_, rhs.n_ );
}
/* dtor */
DenseMatrix::~DenseMatrix() {
std::cout << "dtor called." << std::endl;
delete[] values;
}
- matrix_trial.cpp
#include <iostream>
#include <utility>
#include "densematrix.h"
int main( int argc, char* argv[] ) {
/* ctor */
DenseMatrix A( 5, 10 );
/* ctor */
DenseMatrix B( 10, argc );
/* copy ctor */
DenseMatrix C = A;
/* copy assignment */
C = B;
/* move ctor */
DenseMatrix D( A*B );
DenseMatrix E = DenseMatrix( 100, 200 );
/* move assignment */
D = C*D;
return 0;
}
If I compile my program without the -fno-elide-constructors switch, I obtain the following output:
ctor with two arguments called.
ctor with two arguments called.
copy ctor called.
copy assignment called.
ctor with two arguments called.
ctor with two arguments called.
ctor with two arguments called.
move assignment called.
dtor called.
dtor called.
dtor called.
dtor called.
dtor called.
dtor called.
If, on the other hand, I compile using the -fno-elide-constructors switch, I get the following output:
ctor with two arguments called.
ctor with two arguments called.
copy ctor called.
copy assignment called.
ctor with two arguments called.
move ctor called.
dtor called.
move ctor called.
dtor called.
ctor with two arguments called.
move ctor called.
dtor called.
ctor with two arguments called.
move ctor called.
dtor called.
move assignment called.
dtor called.
dtor called.
dtor called.
dtor called.
dtor called.
dtor called.
In the second output, I am confused by the move ctor's. First, two move ctor's are called in creating D, whereas only one move ctor is called in creating E. Second, in assigning the result of the multiplication to D, another move ctor is called before the move assignment operator.
Could someone explain what is happening and/or if I have designed my class correctly? What should I do in this situation? Shall I compile the program in a normal way (without the switch) and use std::move() whenever I would like to ensure move semantics, or what?
Thank you!
Edit due to the comments of @Praetorian:
The final version of my densematrix.h
implementation is then as follows:
#ifndef DENSEMATRIX_H_
#define DENSEMATRIX_H_
#include <cstddef>
#include <stdexcept>
#include <utility>
#include <vector>
class DenseMatrix {
private:
size_t m_ = 0, /* number of rows */
n_ = 0; /* number of columns */
/* values of the matrix in column major order */
std::vector< double > values_;
public:
/* ctor */
DenseMatrix( size_t m, size_t n,
std::vector< double > values = std::vector< double >() ) :
m_( m ),
n_( n ),
values_( values )
{
if ( m_*n_ == 0 )
throw std::domain_error( "One of the matrix dimensions is zero!" );
else if ( m_*n_ != values.size() && values_.size() != 0 )
throw std::domain_error( "Matrix dimensions do not match with the number of elements" );
}
/* copy ctor */
DenseMatrix( const DenseMatrix& rhs ) :
m_( rhs.m_ ),
n_( rhs.n_ ),
values_( rhs.values_ ) { }
/* move ctor */
DenseMatrix( DenseMatrix&& rhs ) noexcept :
m_( std::move( rhs.m_ ) ),
n_( std::move( rhs.n_ ) ),
values_( std::move( rhs.values_ ) ) { }
/* copy assignment */
const DenseMatrix& operator=( const DenseMatrix& rhs ) {
if ( this != &rhs ) {
m_ = rhs.m_;
n_ = rhs.n_;
/* trust std::vector<>'s optimized implementation, i.e.,
* no need to check the vectors' sizes to decrease the
* heap access */
values_ = rhs.values_;
}
return *this;
}
/* move assignment */
const DenseMatrix& operator=( DenseMatrix&& rhs ) noexcept {
m_ = std::move( rhs.m_ );
n_ = std::move( rhs.n_ );
values_ = std::move( rhs.values_ );
return *this;
}
/* matrix multiplication */
DenseMatrix operator*( const DenseMatrix& rhs ) const {
/* do dimension checking */
DenseMatrix temp( this->m_, rhs.n_ );
/* do the necessary calculations */
return temp;
}
/* dtor not needed in this case */
};
#endif
Now, hopefully, I have implemented the semantics correctly. What do you think? Now, I am depending on the container class I am using when it comes to copying and/or moving vectors of different sizes.
Thank you again for your help and comments!