10 Replies - 822 Views - Last Post: 28 November 2014 - 05:35 PM Rate Topic: -----

#1 Anarion   User is offline

  • The Persian Coder
  • member icon

Reputation: 387
  • View blog
  • Posts: 1,663
  • Joined: 16-May 09

Problem Regarding Implementation Inheritance

Posted 24 November 2014 - 12:20 PM

Hello guys, I have been working on a customized matrix object for use in my project. Well, to be honest, I was going to use Eigen to avoid re-inventing the wheel but then again, I took it as a challenge, especially that I haven't programmed for so long.

So, here's my problem in a nutshell: to optimize performance, I decided to store the matrix elements in a row-wise flat vector (std::vector<double> instead of std::vector<std::vector<double> >. I got it working the way I wanted it to be, I can access rows or columns and the iteration works just fine. After doing all of these, I decided that the performance of iteration would be improved further if I implement this matrix class in two variants, one row-wise (which has been implemented) and one column-wise so that I can use the latter when I need heavy calculations on different columns.

To implement such mechanics, I have tried to derive from the row-wise class and overload just those functions which define the element access operations. So, basically, I planned to have two classes which are exactly the same in interface and internal members, except for a few functions which have to be re-implemented to define the element retrieval. Here's the class declarations:
class matrix_base
{

public:

    matrix_base()=delete;
    matrix_base(const std::size_t&, const std::size_t&);
    matrix_base(const matrix_base&);
    matrix_base(const matrix_base&&);

    matrix_base& operator=(const matrix_base&);
    matrix_base& operator=(matrix_base&&);

    double& operator()(const std::size_t&, const std::size_t&);
    const double& operator()(const std::size_t&, const std::size_t&) const;

    //these two functions must be redefined by the derived class
    virtual iterator cbegin(const std::size_t&);
    virtual iterator rbegin(const std::size_t&);

    iterator begin();

    void assign(std::initializer_list<double>);

    friend matrix_base operator+(const matrix_base&, const matrix_base&);
    friend matrix_base operator-(const matrix_base&, const matrix_base&);

    friend std::ostream& operator<<(std::ostream&, const matrix_base&);

    size_t row_count() const; //return the number of rows
    size_t col_count() const; //return the number of columns
    size_t size() const;

protected:

    std::vector<double> _v; //a contiguous vector
    std::size_t _n; //number of rows
    std::size_t _m; //number of columns

    //convert between 2-index method and 1-index method
    virtual std::size_t linear_index(const std::size_t&, const std::size_t&) const;

};

typedef matrix_base matrixrw; //just a typical second name for convenience, stands for row-wise matrix


Here's what I did for the derivation, which is problematic:
class matrixcw: public matrix_base
{

public:

    using matrix_base::matrix_base; //inherit base constructors, correct?

    iterator cbegin(const std::size_t&) override;
    iterator rbegin(const std::size_t&) override;

protected:

    std::size_t linear_index(const std::size_t&, const std::size_t&) const override;

};

As I expected, it should have worked out just fine but things are a little bit weird. The following works:
matrixrw a(2, 2), b(2, 2);
a.assign({1,2,3,4});
b.assign({2,2,2,2});
matrixrw c{a+b};


But if I switch it to the derived class, there comes compilation errors.
matrixcw a(2, 2), b(2, 2);
a.assign({1,2,3,4});
b.assign({2,2,2,2});
matrixcw c{a+b}; //generates compile errors!

Quote

||=== Build: Debug in matrix (compiler: GNU GCC Compiler) ===|
\main.cpp||In function 'int main()':|
\main.cpp|14|error: no matching function for call to 'optimization::matrixcw::matrixcw(optimization::matrix_base)'|
\main.cpp|14|note: candidate is:|
\matrix.h|75|note: optimization::matrixcw::matrixcw(const size_t&, const size_t&)|
\matrix.h|75|note: candidate expects 2 arguments, 1 provided|
||=== Build failed: 1 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|


I managed to narrow down the problem, it turns out that if I define operator+ for the derived class (an exact copy of the base version, just changing the types), everything works.

Why is this happening? Shouldn't it work if I don't redefine operator+?
As a matter of fact, I would like to know if there are better approaches that I am not thinking of :)

Is This A Good Question/Topic? 0
  • +

Replies To: Problem Regarding Implementation Inheritance

#2 CTphpnwb   User is offline

  • D.I.C Lover
  • member icon

Reputation: 3786
  • View blog
  • Posts: 13,717
  • Joined: 08-August 08

Re: Problem Regarding Implementation Inheritance

Posted 24 November 2014 - 12:35 PM

I get this error with your code:
main.cpp:23:10: Use of class template 'iterator' requires template arguments

(Before adding the derived class.)
Was This Post Helpful? 0
  • +
  • -

#3 Anarion   User is offline

  • The Persian Coder
  • member icon

Reputation: 387
  • View blog
  • Posts: 1,663
  • Joined: 16-May 09

Re: Problem Regarding Implementation Inheritance

Posted 24 November 2014 - 12:48 PM

Pardon me for omitting the full implementation. Here are the files containing the re-implementation of operator+, don't forget the C++11 compiler tag: Attached File  matrix.zip (2.61K)
Number of downloads: 61
Side note: Currently, the column-wise operations are dummy and a copy of the row-wise implementation. Also, the iterator is customized to satisfy the needs of my project, that's why they are a little bit weird :)

This post has been edited by Anarion: 24 November 2014 - 12:49 PM

Was This Post Helpful? 0
  • +
  • -

#4 CTphpnwb   User is offline

  • D.I.C Lover
  • member icon

Reputation: 3786
  • View blog
  • Posts: 13,717
  • Joined: 08-August 08

Re: Problem Regarding Implementation Inheritance

Posted 24 November 2014 - 02:35 PM

That code compiles and runs for me. Output:
3 4
10 12
Was This Post Helpful? 0
  • +
  • -

#5 Anarion   User is offline

  • The Persian Coder
  • member icon

Reputation: 387
  • View blog
  • Posts: 1,663
  • Joined: 16-May 09

Re: Problem Regarding Implementation Inheritance

Posted 24 November 2014 - 02:39 PM

View PostCTphpnwb, on 25 November 2014 - 01:05 AM, said:

That code compiles and runs for me. Output:
3 4
10 12

Yes, indeed. My problem is that I don't understand why it works now (that has the re-definition of operator+ in matrixcw) but does not work when I omit this re-definition. I expected it to work because the base class defines it already. There must be something I am missing here.

Edit:Here is what I mean:
class matrixcw: public matrix_base
{

public:

    using matrix_base::matrix_base;

    iterator cbegin(const std::size_t&) override;
    iterator rbegin(const std::size_t&) override;

    //Right here, if you comment this out (along with it's definition)
    //the code does not compile
    friend matrixcw operator+(const matrixcw&, const matrixcw&);

protected:

    std::size_t linear_index(const std::size_t&, const std::size_t&) const override;

};

This post has been edited by Anarion: 24 November 2014 - 02:41 PM

Was This Post Helpful? 0
  • +
  • -

#6 CTphpnwb   User is offline

  • D.I.C Lover
  • member icon

Reputation: 3786
  • View blog
  • Posts: 13,717
  • Joined: 08-August 08

Re: Problem Regarding Implementation Inheritance

Posted 24 November 2014 - 02:50 PM

It defines it, but for the base class. I think you need to use templates here, but I always seem to mess up inheritance when using them. :sadlike:
Was This Post Helpful? 1
  • +
  • -

#7 CTphpnwb   User is offline

  • D.I.C Lover
  • member icon

Reputation: 3786
  • View blog
  • Posts: 13,717
  • Joined: 08-August 08

Re: Problem Regarding Implementation Inheritance

Posted 24 November 2014 - 03:03 PM

I thought this might do it, but I'm missing something too:
template <class T>
T operator+(const T& lh, const T& rh)
{
   if(lh.row_count() != rh.row_count() || lh.col_count() != rh.col_count())
   {
      throw std::runtime_error("addition of matrices with non-equal size");
   }
   T tmp(lh.row_count(), lh.col_count());
   for(size_t i=0; i != rh.size(); ++i)
   {
      tmp._v[i] = lh._v[i] + rh._v[i];
   }
   return tmp;
}


The thing is that the operators aren't class methods, they're just stand alone functions.
Was This Post Helpful? 1
  • +
  • -

#8 Anarion   User is offline

  • The Persian Coder
  • member icon

Reputation: 387
  • View blog
  • Posts: 1,663
  • Joined: 16-May 09

Re: Problem Regarding Implementation Inheritance

Posted 24 November 2014 - 03:16 PM

View PostCTphpnwb, on 25 November 2014 - 01:20 AM, said:

It defines it, but for the base class. I think you need to use templates here, but I always seem to mess up inheritance when using them. :sadlike:

Ah tell me about it! I have always had a bad feeling towards inheritance, now look how I got trapped again into it :dontgetit:
After a little bit of testing, it turns out that the copy constructor is not working for the derived class as well. I think I read something wrong in my textbook, because from what I have read, this should be working fine because matrixcw is not adding any invariants...

I agree with you that I should try templates. After all, it's only a part of the behavior that is changing. I am thinking about passing a function object as template argument right now.

Will post the results back if it works elegantly this way :mellow: Let me know if you find something.

Oh and thanks for helping me out my friend :)

This post has been edited by Anarion: 24 November 2014 - 03:18 PM

Was This Post Helpful? 0
  • +
  • -

#9 CTphpnwb   User is offline

  • D.I.C Lover
  • member icon

Reputation: 3786
  • View blog
  • Posts: 13,717
  • Joined: 08-August 08

Re: Problem Regarding Implementation Inheritance

Posted 24 November 2014 - 03:47 PM

Ok, so I'm closer to a solution. See if you follow me.

You're doing something like this:
#include <iostream>

using namespace std;

class Foo {
	int x, y;
public:
	Foo(int a, int b ):x(a), y( b ) {}
	int getx() { return x; }
	int gety() { return y; }
	void show() {
		cout << x << ", " << y << endl;
	}
};

class Bar:public Foo {
	int z;
public:
	Bar(int a, int b, int c):Foo(a,b ),z(c) {}
	void show() {
		cout << getx() << ", " << gety() << ", " << z << endl;
	}

};

Foo operator+(Foo &A, Foo & B ) {
	Foo tmp(A.getx()+B.getx(),A.gety()+B.gety());
	return tmp;
}
int main(int argc, const char * argv[]) {
	Foo A(1,2), B(10,20);
	Foo C = A + B;
	C.show();

	Foo D(A + B );
	D.show();
	return 0;
}



But you need something like this:
#include <iostream>

using namespace std;

class Foo {
	int x, y;
public:
	Foo(int a, int b ):x(a), y( b ) {}
	int getx() { return x; }
	int gety() { return y; }
	void show() {
		cout << x << ", " << y << endl;
	}
};

class Bar:public Foo {
	int z;
public:
	Bar(int a, int b ):Foo(a,b ), z(0) {}
	Bar(int a, int b, int c):Foo(a,b ),z(c) {}
	void show() {
		cout << getx() << ", " << gety() << ", " << z << endl;
	}

};

template <class T>
T operator+(T &A, T & B ) {
	T tmp(A.getx()+B.getx(),A.gety()+B.gety());
	return tmp;
}

int main(int argc, const char * argv[]) {
	Foo A(1,2), B(10,20);
	Foo C = A + B;
	C.show();

	Foo D(A + B );
	D.show();

	Bar E(101, 102, 33);
	Bar F(10,20,20);
	Bar G = E + F;

	G.show();

	return 0;
}


This post has been edited by CTphpnwb: 24 November 2014 - 03:49 PM

Was This Post Helpful? 1
  • +
  • -

#10 CTphpnwb   User is offline

  • D.I.C Lover
  • member icon

Reputation: 3786
  • View blog
  • Posts: 13,717
  • Joined: 08-August 08

Re: Problem Regarding Implementation Inheritance

Posted 25 November 2014 - 09:14 AM

Got it! The template needs to be in the header file and a friend of the base class.
matrix.h
#ifndef MATRIX_H_INCLUDED
#define MATRIX_H_INCLUDED

#include <vector>
#include <initializer_list>
#include <ostream>
#include <stdexcept>
#include "iterator.h"

namespace optimization
{



	/*
	 * A specialized 2d matrix class for use in optimization purposes
	 */
	class matrix_base
	{

	public:

		matrix_base()=delete; //disable default constructor, force the use of the following
		matrix_base(const std::size_t&, const std::size_t&); //ordinary constructor
		matrix_base(const matrix_base&); //copy constructor
		matrix_base(const matrix_base&&); //move constructor

		matrix_base& operator=(const matrix_base&); //copy assignment only for matrix types
		matrix_base& operator=(matrix_base&&); //move assignment

		double& operator()(const std::size_t&, const std::size_t&);
		const double& operator()(const std::size_t&, const std::size_t&) const;

		virtual iterator cbegin(const std::size_t&);
		virtual iterator rbegin(const std::size_t&);
		virtual iterator begin(); //used for whole-matrix iteration

		void assign(std::initializer_list<double>);

		template <class T>
		friend T operator+(const T &, const T &);

		friend matrix_base operator-(const matrix_base&, const matrix_base&);

		friend std::ostream& operator<<(std::ostream&, const matrix_base&);

		size_t row_count() const; //return the number of rows
		size_t col_count() const; //return the number of columns
		size_t size() const;

	protected:

		std::vector<double> _v; //a contiguous vector to hold the doubles
		std::size_t _n; //number of rows
		std::size_t _m; //number of columns

		//convert between 2-index method and 1-index method
		virtual std::size_t linear_index(const std::size_t&, const std::size_t&) const;

	};

	typedef matrix_base matrixrw;

	class matrixcw: public matrix_base
	{

	public:

		using matrix_base::matrix_base;

		iterator cbegin(const std::size_t&) override;
		iterator rbegin(const std::size_t&) override;

		template<class T>
		friend T operator+(const T&, const T&);
		//friend matrixcw operator+(const matrixcw&, const matrixcw&);

	protected:

		std::size_t linear_index(const std::size_t&, const std::size_t&) const override;

	};

	template <class T>
	T operator+(const T& lh, const T& rh)
	{
		if(lh.row_count() != rh.row_count() || lh.col_count() != rh.col_count())
		{
			throw std::runtime_error("addition of matrices with non-equal size");
		}
		T tmp(lh.row_count(), lh.col_count());
		for(size_t i=0; i != rh.size(); ++i)
		{
			tmp._v[i] = lh._v[i] + rh._v[i];
		}
		return tmp;
	}
	
} //end of namespace

#endif // MATRIX_H_INCLUDED


matrix.cpp
#include "matrix.h"
#include <algorithm>
#include <utility>
#include <exception>

namespace optimization
{

matrix_base::matrix_base(const size_t& rows, const size_t& cols)
   : _v(rows*cols),
     _n {rows},
     _m {cols}
{
   //nothing to do here
}

matrix_base::matrix_base(const matrix_base& rh): matrix_base(rh._n, rh._m)
{
   _v = rh._v;
}

matrix_base::matrix_base(const matrix_base&& rval)
                : _v {std::move(rval._v)},
                  _n {rval._n},
                  _m {rval._m}
{
   //no need to do anything special here
}

matrix_base& matrix_base::operator=(const matrix_base& rh)
{
   if(_n == rh._n || _m == rh._m)
   {
      _v = rh._v;
   }
   else
   {
      throw std::runtime_error("assignment of matrices with non-equal size");
   }
   return *this;
}

matrix_base& matrix_base::operator=(matrix_base&& rh)
{
   if(_n == rh._n || _m == rh._m)
   {
      _v = std::move(rh._v);
   }
   else
   {
      throw std::runtime_error("assignment of matrices with non-equal size");
   }
   return *this;
}

size_t matrix_base::row_count() const
{
   return _n;
}

size_t matrix_base::col_count() const
{
   return _m;
}

size_t matrix_base::size() const
{
   return _v.size();
}

size_t matrix_base::linear_index(const size_t& i, const size_t& j) const {
    return i*_m + j;
}

double& matrix_base::operator()(const size_t& i, const size_t& j)
{
   return _v.at(linear_index(i, j));
}

const double& matrix_base::operator()(const size_t& i, const size_t& j) const
{
   return _v.at(linear_index(i, j));
}

void matrix_base::assign(std::initializer_list<double> ilist)
{
   if(ilist.size() != this->size())
   {
      throw std::runtime_error("inserting wrong count of data into matrix");
   }
   _v.assign(ilist.begin(), ilist.end());
}

iterator matrix_base::cbegin(const size_t& j)
{
   return iterator(&_v[j], &_v[_n*_m+j], _m);
}

iterator matrix_base::rbegin(const size_t& i)
{
   return iterator(&_v[i*_m], &_v[i*_m+_m]);
}

iterator matrix_base::begin()
{
   return iterator(&_v[0], &_v[0+_v.size()]);
}

//EDIT
iterator matrixcw::cbegin(const size_t& j)
{
   return iterator(&_v[j], &_v[_n*_m+j], _m);
}

iterator matrixcw::rbegin(const size_t& i)
{
   return iterator(&_v[i*_m], &_v[i*_m+_m]);
}

size_t matrixcw::linear_index(const size_t& i, const size_t& j) const {
    return i*_m + j;
}


matrix_base operator-(const matrix_base& lh, const matrix_base& rh)
{
   if(lh.row_count() != rh.row_count() || lh.col_count() != rh.col_count())
   {
      throw std::runtime_error("addition of matrices with non-equal size");
   }
   matrix_base tmp(lh.row_count(), lh.col_count());
   for(size_t i=0; i != rh.size(); ++i)
   {
      tmp._v[i] = lh._v[i] - rh._v[i];
   }
   return tmp;
}

std::ostream& operator<<(std::ostream& o, const matrix_base& m)
{
   for(size_t i=0; i<m._n; ++i)
   {
      for(size_t j=0; j<m._m; ++j)
      {
         o<<m(i, j)<<" ";
      }
      o<<std::endl;
   }
   return o;
}

} //end of namespace



You'll want to do the same for the other operators.
Was This Post Helpful? 1
  • +
  • -

#11 Anarion   User is offline

  • The Persian Coder
  • member icon

Reputation: 387
  • View blog
  • Posts: 1,663
  • Joined: 16-May 09

Re: Problem Regarding Implementation Inheritance

Posted 28 November 2014 - 05:35 PM

Finally! After long hours of torturing my soul, my problems were cleared. Here's a clear statement of the problem I had encountered and the solution (Special thanks to CTphpnwb for helping me out).

What was I trying to achieve? To practice and sharpen my skills (ehem, read "lack of skills") after a very long time, I decided to implement a specialized matrix class which is the heart of my future projects. One of the topics that I had never fully used before was inheritance. For this particular project, implementation inheritance seemed to be a good design approach to me: mainly to avoid code duplication which welcomes more errors.

To illustrate the solution better, see this example which exactly follows the design approach of my matrix class:
class base
{
public:
   base(): base(0)
   {
      cout<<"base::base() was called -> initialize _a to 0"<<endl;
   }
   base(int a): _a {a} {cout<<"base::base(int) was called -> initialize _a to "<<a<<endl;}
   base(base const& rhs): base(rhs._a)
   {
      cout<<"base::base(base) was called -> initialize _a to "<<_a<<endl;
   }
   base& operator=(base const& rhs)
   {
      _a = rhs._a;
      cout<<"base::operator=(&) was called"<<endl;
      return *this;
   }
   base& operator=(base&& rhs)
   {
      _a = std::move(rhs._a);
      cout<<"base::operator=(&&) was called"<<endl;
      return *this;
   }
   int get() const
   {
      return _a;
   }
   virtual void f()
   {
      cout<<"base::f() was called"<<endl;
   }
protected:
   int _a;
};

The above code implements a fully operational class. It's not an abstract, because I am not going to use it for interface inheritance. This is a key point. My plan is to derive from this class, inherit every single member (functions and invariants), modify just a very small portion of it, and get two classes that are strongly dependent to each other (one implements nearly all of the functionality, the other one is basically the same as the first one, with a small modification to it's behavior).

To achieve such relationship between two classes, and also to minimize code duplication, I needed one of C++11's new additions: inheriting constructors. Lets take a look at the derived class:
class derived: public base
{
public:
   using base::base; //inherit base's constructors, all of them!
   void f() override
   {
      cout<<"derived::f() was called"<<endl;
   }
};

Notice how derived is being implemented: it's like base, with the exception of a single function, which gets re-implemented in the derived class to provide customized behavior.
Now I have two classes, base and derived. As a required feature, operator+ must be provided for both of these classes. As you know, this operation returns by value (move semantics should be applied here, or better, copy elision by the compiler!). So, there is no option for returning a polymorphic type (a reference or pointer to base, which can also point or refer to derived). So, in order to provide operator+ for both of these classes, two separate definitions are required:
base operator+(base const& lhs, base const& rhs)
{
   return base{lhs.get() + rhs.get()};
}

derived operator+(derived const& lhs, derived const& rhs)
{
   return derived{lhs.get() + rhs.get()};
}

There is one more problem. The above operators don't provide such functionality as derived d = derived{4} + base{2}. This kind of addition requires another definition:
derived operator+(derived const& lhs, base const& rhs)
{
   return derived{lhs.get() + rhs.get()};
}

The above method is error-prone: I had to duplicate the same code with different types. This is where templates come into play, as CTphpnwb noted. The solution is:
template <class T1, class T2>
T1 operator+(T1 const& lhs, T2 const& rhs)
{
   return lhs.get() + rhs.get();
}

Now, all combinations are generated by the compiler if necessary.

So, this was how my problem was solved! Although as I wrote this post, all of the code was changed to reflect a new design approach: using templates to provide portions of behavior which need to be changed, and eliminate inheritance for good. For the sake of brevity, lets skip over the details ;)

This post has been edited by Anarion: 28 November 2014 - 05:43 PM

Was This Post Helpful? 0
  • +
  • -

Page 1 of 1