Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CPlusPlusNotesForProfessionals.pdf
Скачиваний:
47
Добавлен:
20.05.2023
Размер:
5.11 Mб
Скачать

Chapter 36: Operator Overloading

In C++, it is possible to define operators such as + and -> for user-defined types. For example, the <string> header defines a + operator to concatenate strings. This is done by defining an operator function using the operator keyword.

Section 36.1: Arithmetic operators

You can overload all basic arithmetic operators:

+ and +=

- and -=

* and *=

/ and /=

& and &=

| and |=

^ and ^=

>> and >>=

<< and <<=

Overloading for all operators is the same. Scroll down for explanation

Overloading outside of class/struct:

//operator+ should be implemented in terms of operator+= T operator+(T lhs, const T& rhs)

{

lhs += rhs; return lhs;

}

T& operator+=(T& lhs, const T& rhs)

{

//Perform addition return lhs;

}

Overloading inside of class/struct:

//operator+ should be implemented in terms of operator+= T operator+(const T& rhs)

{

*this += rhs; return *this;

}

T& operator+=(const T& rhs)

{

//Perform addition return *this;

}

Note: operator+ should return by non-const value, as returning a reference wouldn't make sense (it returns a new

object) nor would returning a const value (you should generally not return by const). The first argument is passed

GoalKicker.com – C++ Notes for Professionals

199

by value, why? Because

1.You can't modify the original object (Object foobar = foo + bar; shouldn't modify foo after all, it wouldn't make sense)

2.You can't make it const, because you will have to be able to modify the object (because operator+ is implemented in terms of operator+=, which modifies the object)

Passing by const& would be an option, but then you will have to make a temporary copy of the passed object. By passing by value, the compiler does it for you.

operator+= returns a reference to the itself, because it is then possible to chain them (don't use the same variable though, that would be undefined behavior due to sequence points).

The first argument is a reference (we want to modify it), but not const, because then you wouldn't be able to modify it. The second argument should not be modified, and so for performance reason is passed by const& (passing by const reference is faster than by value).

Section 36.2: Array subscript operator

You can even overload the array subscript operator [].

You should always (99.98% of the time) implement 2 versions, a const and a not-const version, because if the object is const, it should not be able to modify the object returned by [].

The arguments are passed by const& instead of by value because passing by reference is faster than by value, and const so that the operator doesn't change the index accidentally.

The operators return by reference, because by design you can modify the object [] return, i.e:

std::vector<int> v{ 1 };

v[0] = 2; //Changes value of 1 to 2

//wouldn't be possible if not returned by reference

You can only overload inside a class/struct:

//I is the index type, normally an int T& operator[](const I& index)

{

//Do something //return something

}

//I is the index type, normally an int const T& operator[](const I& index) const

{

//Do something //return something

}

Multiple subscript operators, [][]..., can be achieved via proxy objects. The following example of a simple rowmajor matrix class demonstrates this:

template<class T>

GoalKicker.com – C++ Notes for Professionals

200

class matrix {

// class enabling [][] overload to access matrix elements template <class C>

class proxy_row_vector {

using reference = decltype(std::declval<C>()[0]);

using const_reference = decltype(std::declval<C const>()[0]); public:

proxy_row_vector(C& _vec, std::size_t _r_ind, std::size_t _cols) : vec(_vec), row_index(_r_ind), cols(_cols) {}

const_reference operator[](std::size_t _col_index) const { return vec[row_index*cols + _col_index];

}

reference operator[](std::size_t _col_index) { return vec[row_index*cols + _col_index];

}

private:

C& vec;

std::size_t row_index; // row index to access std::size_t cols; // number of columns in matrix

};

using const_proxy = proxy_row_vector<const std::vector<T>>; using proxy = proxy_row_vector<std::vector<T>>;

public:

matrix() : mtx(), rows(0), cols(0) {} matrix(std::size_t _rows, std::size_t _cols)

:mtx(_rows*_cols), rows(_rows), cols(_cols) {}

//call operator[] followed by another [] call to access matrix elements const_proxy operator[](std::size_t _row_index) const {

return const_proxy(mtx, _row_index, cols);

}

proxy operator[](std::size_t _row_index) { return proxy(mtx, _row_index, cols);

}

private: std::vector<T> mtx; std::size_t rows; std::size_t cols;

};

Section 36.3: Conversion operators

You can overload type operators, so that your type can be implicitly converted into the specified type.

The conversion operator must be defined in a class/struct:

operator T() const { /* return something */ }

Note: the operator is const to allow const objects to be converted.

Example:

struct Text

{

std::string text;

// Now Text can be implicitly converted into a const char* /*explicit*/ operator const char*() const { return text.data(); }

GoalKicker.com – C++ Notes for Professionals

201

//^^^^^^^

//to disable implicit conversion

};

Text t;

t.text = "Hello world!";

//Ok

const char* copyoftext = t;

Section 36.4: Complex Numbers Revisited

The code below implements a very simple complex number type for which the underlying field is automatically promoted, following the language's type promotion rules, under application of the four basic operators (+, -, *, and /) with a member of a di erent field (be it another complex<T> or some scalar type).

This is intended to be a holistic example covering operator overloading alongside basic use of templates.

#include <type_traits>

namespace not_std{

using std::decay_t;

//----------------------------------------------------------------

// complex< value_t > //----------------------------------------------------------------

template<typename value_t> struct complex

{

value_t x; value_t y;

complex &operator += (const value_t &x)

{

this->x += x; return *this;

}

complex &operator += (const complex &other)

{

this->x += other.x; this->y += other.y; return *this;

}

complex &operator -= (const value_t &x)

{

this->x -= x; return *this;

}

complex &operator -= (const complex &other)

{

this->x -= other.x; this->y -= other.y; return *this;

}

complex &operator *= (const value_t &s)

{

GoalKicker.com – C++ Notes for Professionals

202

this->x *= s; this->y *= s; return *this;

}

complex &operator *= (const complex &other)

{

(*this) = (*this) * other; return *this;

}

complex &operator /= (const value_t &s)

{

this->x /= s; this->y /= s; return *this;

}

complex &operator /= (const complex &other)

{

(*this) = (*this) / other; return *this;

}

complex(const value_t &x, const value_t &y) : x{x}

, y{y} {}

template<typename other_value_t>

explicit complex(const complex<other_value_t> &other) : x{static_cast<const value_t &>(other.x)}

, y{static_cast<const value_t &>(other.y)} {}

complex &operator = (const complex &) = default; complex &operator = (complex &&) = default; complex(const complex &) = default; complex(complex &&) = default;

complex() = default;

};

// Absolute value squared template<typename value_t>

value_t absqr(const complex<value_t> &z) { return z.x*z.x + z.y*z.y; }

//----------------------------------------------------------------

// operator - (negation) //----------------------------------------------------------------

template<typename value_t>

complex<value_t> operator - (const complex<value_t> &z) { return {-z.x, -z.y}; }

//----------------------------------------------------------------

// operator + //----------------------------------------------------------------

template<typename left_t,typename right_t>

auto operator + (const complex<left_t> &a, const complex<right_t> &b) -> complex<decay_t<decltype(a.x + b.x)>>

{ return{a.x + b.x, a.y + b.y}; }

GoalKicker.com – C++ Notes for Professionals

203

template<typename left_t,typename right_t>

auto operator + (const left_t &a, const complex<right_t> &b) -> complex<decay_t<decltype(a + b.x)>>

{ return{a + b.x, b.y}; }

template<typename left_t,typename right_t>

auto operator + (const complex<left_t> &a, const right_t &b) -> complex<decay_t<decltype(a.x + b)>>

{ return{a.x + b, a.y}; }

//----------------------------------------------------------------

// operator - //----------------------------------------------------------------

template<typename left_t,typename right_t>

auto operator - (const complex<left_t> &a, const complex<right_t> &b) -> complex<decay_t<decltype(a.x - b.x)>>

{ return{a.x - b.x, a.y - b.y}; }

template<typename left_t,typename right_t>

auto operator - (const left_t &a, const complex<right_t> &b) -> complex<decay_t<decltype(a - b.x)>>

{ return{a - b.x, - b.y}; }

template<typename left_t,typename right_t>

auto operator - (const complex<left_t> &a, const right_t &b) -> complex<decay_t<decltype(a.x - b)>>

{ return{a.x - b, a.y}; }

//----------------------------------------------------------------

// operator * //----------------------------------------------------------------

template<typename left_t, typename right_t>

auto operator * (const complex<left_t> &a, const complex<right_t> &b) -> complex<decay_t<decltype(a.x * b.x)>>

{

return {

a.x*b.x - a.y*b.y, a.x*b.y + a.y*b.x };

}

template<typename left_t, typename right_t>

auto operator * (const left_t &a, const complex<right_t> &b) -> complex<decay_t<decltype(a * b.x)>>

{ return {a * b.x, a * b.y}; }

template<typename left_t, typename right_t>

auto operator * (const complex<left_t> &a, const right_t &b) -> complex<decay_t<decltype(a.x * b)>>

{ return {a.x * b, a.y * b}; }

//----------------------------------------------------------------

// operator / //----------------------------------------------------------------

template<typename left_t, typename right_t>

auto operator / (const complex<left_t> &a, const complex<right_t> &b) -> complex<decay_t<decltype(a.x / b.x)>>

{

const auto r = absqr(b);

GoalKicker.com – C++ Notes for Professionals

204

return {

( a.x*b.x + a.y*b.y) / r, (-a.x*b.y + a.y*b.x) / r };

}

template<typename left_t, typename right_t>

auto operator / (const left_t &a, const complex<right_t> &b) -> complex<decay_t<decltype(a / b.x)>>

{

const auto s = a/absqr(b); return {

b.x * s, -b.y * s };

}

template<typename left_t, typename right_t>

auto operator / (const complex<left_t> &a, const right_t &b) -> complex<decay_t<decltype(a.x / b)>>

{ return {a.x / b, a.y / b}; }

}// namespace not_std

int main(int argc, char **argv)

{

using namespace not_std;

complex<float> fz{4.0f, 1.0f};

//makes a complex<double> auto dz = fz * 1.0;

//still a complex<double> auto idz = 1.0f/dz;

//also a complex<double> auto one = dz * idz;

//a complex<double> again auto one_again = fz * idz;

//Operator tests, just to make sure everything compiles.

complex<float> a{1.0f, -2.0f}; complex<double> b{3.0, -4.0};

//All of these are complex<double> auto c0 = a + b;

auto c1 = a - b; auto c2 = a * b; auto c3 = a / b;

//All of these are complex<float> auto d0 = a + 1;

auto d1 = 1 + a; auto d2 = a - 1; auto d3 = 1 - a; auto d4 = a * 1; auto d5 = 1 * a; auto d6 = a / 1;

GoalKicker.com – C++ Notes for Professionals

205