Friday, 17 February 2012

Operator Overloading in C++ Part 2

In the previous post I discussed some of the basics of operator overloading and the difference between "Free Operators" and "Member Operators" and we overloaded the << insertion operator so we could use our Vec3 class with the std::cout method, in this section I'm going to start with the assignment operator which is one of the operators which must be a Member operator.

The assignment Operator
The assignment operator must be a member operator to ensure that they receive an lvalue (basically an expression that refers to the object) as the first operand, also if we follow the "rule of three" we should define a copy constructor as well, in this case we could get away without writing an assignment operator as we don't create any dynamic memory in the class, however good practice dictates it's best to always do this. If we did any dynamic allocation in this class we would need to do a "deep copy". The following code is added to both version of the project code to add the copy constructor and assignment operator.

/// @brief a copy ctor
Vec3(const Vec3 &_v);

/// @brief assignment operator
Vec3 & operator=(const Vec3 &_rhs);
In the body code I've explicitly made calls to print out which method is being called so we can see the order of the operations
Vec3::Vec3(const Vec3 &_v)
  std::cout<<"copy ctor called\n";

Vec3 & Vec3::operator=(const Vec3 &_rhs)
  std::cout<<"assignment operator called\n";
  return *this;
We can now run the following code and see them being run
Vec3 a(1,2,3);
Vec3 b;
Vec3 c(2,3,4);
std::cout<<"a "<<a<<" b "<<b<<" c "<<c<<"\n";
std::cout<<"testing the assignment operator\n";
std::cout<<"testing the copy ctor\n";
Vec3 copy(a);
Which gives the following output
a [1,2,3] b [0,0,0] c [2,3,4]
assignment operator called
copy ctor called
We can also create more than one assignment operator passing in different types to the right hand side parameter, for example if we had a 4x4 matrix class we could call Mat4x4 v=1.0 to set the values to the identity matrix. The following example will set all of the Vec3 components to the floating point value passed in
/// @brief assignment operator from float
Vec3 & operator=(float _rhs);

in .cpp

Vec3 & Vec3::operator=(float _rhs)
  std::cout<<"assignment operator from float called\n";
  return *this;
We can now use this to assign our Vec3 from a single float value as shown
Vec3 fromFloat;
Which gives the following output
assignment operator from float called
assignment operator from float called
Obviously caution should be applied to this approach in your design, however it is quite common in graphical applications to be able to do this (for example renderman tuple types and glsl Vec types both support this type of assignment so I have used it in ngl)
The [] operator
The [] subscript operator is a useful operator to overload as it will allow assignment and access to the internal elements of the class. This does however have the side effect of exposing these private attributes and break encapsulation.

To access the data we really need to have an array as well as the individual m_x style elements, to do this we can use a union however this will effectively make things public anyway (see this post about how g++ does this but not clang ).  The following code will modify the class so we have a union

  float m_x;
  float m_y;
  float m_z; 
 float m_array[3];
To write the subscript operator we use the following operator syntax
/// @brief [] operator
float & operator[](unsigned int _index);
And in the .cpp file we add
/// @brief [] operator
float & Vec3::operator[](unsigned int _index)
  assert(_index<3 );
  return m_array[_index];
You will notice in the above example the use of the assert function (from #include <cassert>) this will abort the program if the index is out of range, in this example as we are using an unsigned int for the index we only need to check for the higher bound as the index can never be negative. However our client code could use an int for the index which we should also check the negative bound, to make a fully safe version we should really write both [int] and [unsigned int] versions of the code. Testin this code with the following program
std::cout<<"testing the  [] operator \n";
std::cout<<fromFloat[0]<<" "<<fromFloat[1]<<" "<<fromFloat[2] <<"\n";
Which will give the following output (and abort due to the assert)
testing the  [] operator 
2 99 3
Assertion failed: (_index<3), function operator[], file Vec3.cpp, line 42.
The program has unexpectedly finished.
Note : it is also possible to write the same operator without the union, by using the following code
 return &m_x[_index];
Whilst this will work it does make several assumptions on how the data is packed in your header,in this case we assume packed alignment and that m_x m_y and m_z are contiguous, while this is generally the case it is not the safest way of accessing the data and the union should always ensue that the correct data packing is in place.

No comments:

Post a Comment