05 - compound assignment
Compound assignment operators can be defined both as free functions and as member functions. Member functions are preferred because their implementation is simpler and since the operation has no symmetry (a += b
is not the same as b += a
for non-numeric abstractions, e.g. strings) there are no problems with inconsistent implicit convertion.
Both free and member function implementations can return a reference to allow chained operations like a = b = c = d
(most commonly done with operators <<
and >>
) (member function uses return *this;
statement, free function returns first parameter which for the cause of modification is taken by non-const reference).
To avoid code duplication, there are 2 choices:
compound assignments reuse other operators
other operators reuse compound assignments
The second one is preferred. Reason: just simpler and less code to write.
Updated code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
#include <iostream> #include <cassert> // (greatest common divisor) // if you have C++17, you can remove this function and use std::gcd from <numeric> int gcd(int a, int b) { if (b == 0) return a; else return gcd(b, a % b); } class fraction { private: int m_numerator; int m_denominator; static int make_valid_denominator(int value) { if (value == 0) return 1; else return value; } public: fraction(int numerator = 0, int denominator = 1) : m_numerator(numerator) , m_denominator(make_valid_denominator(denominator)) {} void simplify() { const int n = gcd(m_numerator, m_denominator); m_numerator /= n; m_denominator /= n; } int numerator() const { return m_numerator; } int denominator() const { return m_denominator; } fraction& operator+=(fraction rhs) { m_numerator = numerator() * rhs.denominator() + rhs.numerator() * denominator(); m_denominator = denominator() * rhs.denominator(); simplify(); return *this; } fraction& operator-=(fraction rhs) { m_numerator = numerator() * rhs.denominator() - rhs.numerator() * denominator(); m_denominator = denominator() * rhs.denominator(); simplify(); return *this; } fraction& operator*=(fraction rhs) { m_numerator *= rhs.numerator(); m_denominator *= rhs.denominator(); simplify(); return *this; } fraction& operator/=(fraction rhs) { // note: we could write assert(rhs != 0) but // at this point in code operator!= has not been defined yet // a different solution would be to declare operator/= and implement it later assert(rhs.numerator() != 0); m_numerator *= rhs.denominator(); m_denominator *= rhs.numerator(); simplify(); return *this; } fraction& operator%=(fraction rhs) { assert(rhs.numerator() != 0); m_numerator = (numerator() * rhs.denominator()) % (rhs.numerator() * denominator()); m_denominator *= rhs.denominator(); simplify(); return *this; } }; bool operator==(fraction lhs, fraction rhs) { if (lhs.denominator() == rhs.denominator()) return lhs.numerator() == rhs.numerator(); // a/b == c/d is same as ad/bd == bc/bd // we don't need to compute new denominators, just compare ad and bc return lhs.numerator() * rhs.denominator() == rhs.numerator() * lhs.denominator(); } bool operator!=(fraction lhs, fraction rhs) { return !(lhs == rhs); } // Just look how short these are! // The expression modifies lhs which is then returned by reference (from // compound version) which is then returned by value from the non-compound version. // If the type is expensive, second parameter should be taken by const reference // (first by value because it has to be copied, modified and returned). fraction operator+(fraction lhs, fraction rhs) { return lhs += rhs; } fraction operator-(fraction lhs, fraction rhs) { return lhs -= rhs; } fraction operator*(fraction lhs, fraction rhs) { return lhs *= rhs; } fraction operator/(fraction lhs, fraction rhs) { return lhs /= rhs; } fraction operator%(fraction lhs, fraction rhs) { return lhs %= rhs; } int main() { assert(fraction(1, 2) + fraction(1, 4) == fraction(3, 4)); assert(fraction(1, 2) - fraction(1, 4) == fraction(1, 4)); assert(fraction(3, 5) * fraction(4, 3) == fraction(4, 5)); assert(fraction(3, 5) / fraction(3, 4) == fraction(4, 5)); assert(fraction(3, 5) * fraction(4, -3) == fraction(-4, 5)); assert(fraction(3, 5) / fraction(3, -4) == fraction(-4, 5)); assert(fraction(2, 1) % fraction(3, 10) == fraction(1, 5)); assert(fraction(2, 1) % fraction(3, -10) == fraction(1, 5)); assert(fraction(2, 1) % fraction(-3, 10) == fraction(1, 5)); assert(fraction(-2, 1) % fraction(3, 10) == fraction(-1, 5)); assert(fraction(2, -1) % fraction(3, 10) == fraction(-1, 5)); } |