I am trying to learn idiomatic Modern C++. I was wondering how you would improve upon this Fraction class. I would really appreciate any help with code organization, cleanliness and anything else that I should keep in mind.
Header:
#ifndef FRACTION_H
#define FRACTION_H
#include <iostream>
class Fraction {
int numerator{}, denominator{};
public:
Fraction() = default;
Fraction(int n, int d):
numerator(n), denominator(d) {}
~Fraction() = default;
void set_num(int value) { numerator = value; }
void set_den(int value) { denominator = value; }
int get_num() const { return numerator; }
int get_den() const { return denominator; }
void reduce();
int calculate_gcd(int, int) const;
//void display() {}
};
std::istream &operator>>(std::istream &is, Fraction &value);
std::ostream &operator<<(std::ostream &os, const Fraction &value);
Fraction operator+(const Fraction &lhs, const Fraction &rhs);
Fraction operator-(const Fraction &lhs, const Fraction &rhs);
Fraction operator*(const Fraction &lhs, const Fraction &rhs);
Fraction operator/(const Fraction &lhs, const Fraction &rhs);
bool operator> (const Fraction &lhs, const Fraction &rhs);
bool operator< (const Fraction &lhs, const Fraction &rhs);
bool operator==(const Fraction &lhs, const Fraction &rhs);
bool operator!=(const Fraction &lhs, const Fraction &rhs);
bool operator>=(const Fraction &lhs, const Fraction &rhs);
bool operator<=(const Fraction &lhs, const Fraction &rhs);
#endif
Class file:
#include <cmath>
#include "Fraction.h"
void Fraction::reduce() {
int gcd = calculate_gcd(numerator, denominator);
numerator = numerator / gcd;
denominator = denominator / gcd;
}
int Fraction::calculate_gcd(int a, int b) const {
int temp{};
a = std::abs(a);
b = std::abs(b);
if (b > a) {
temp = a;
a = b;
b = temp;
}
while(b != 0) {
temp = b;
b = a % b;
a = temp;
}
return a;
}
std::istream &operator>>(std::istream &is, Fraction &value) {
int numerator{}, denominator{};
is >> numerator >> denominator;
value.set_num(numerator);
value.set_den(denominator);
if (!is) {
numerator = 0;
denominator = 0;
}
return is;
}
std::ostream &operator<<(std::ostream &os, const Fraction &value) {
os << value.get_num() << "/" << value.get_den();
return os;
}
Fraction operator+(const Fraction &lhs, const Fraction &rhs) {
Fraction result{};
result.set_num((lhs.get_num() * rhs.get_den()) + (lhs.get_den() * rhs.get_num()));
result.set_den(lhs.get_den() * rhs.get_den());
result.reduce();
return result;
}
Fraction operator-(const Fraction &lhs, const Fraction &rhs){
Fraction result{};
result.set_num((lhs.get_num() * rhs.get_den()) - (lhs.get_den() * rhs.get_num()));
result.set_den(lhs.get_den() * rhs.get_den());
result.reduce();
return result;
}
Fraction operator*(const Fraction &lhs, const Fraction &rhs){
Fraction result{};
result.set_num(lhs.get_num() * rhs.get_num());
result.set_den(lhs.get_den() * rhs.get_den());
result.reduce();
return result;
}
Fraction operator/(const Fraction &lhs, const Fraction &rhs){
Fraction result{};
result.set_num(lhs.get_num() * rhs.get_den());
result.set_den(lhs.get_den() * rhs.get_num());
result.reduce();
return result;
}
bool operator> (const Fraction &lhs, const Fraction &rhs) {
return (((lhs.get_num() * rhs.get_den()) - (rhs.get_num() * lhs.get_den())) > 0);
}
bool operator< (const Fraction &lhs, const Fraction &rhs) {
return (((lhs.get_num() * rhs.get_den()) - (rhs.get_num() * lhs.get_den())) < 0);
}
bool operator==(const Fraction &lhs, const Fraction &rhs) {
return (((lhs.get_num() * rhs.get_den()) - (rhs.get_num() * lhs.get_den())) == 0);
}
bool operator!=(const Fraction &lhs, const Fraction &rhs) {
return !(lhs == rhs);
}
bool operator>=(const Fraction &lhs, const Fraction &rhs) {
return (lhs > rhs) or (lhs == rhs);
}
bool operator<=(const Fraction &lhs, const Fraction &rhs) {
return (lhs < rhs) or (lhs == rhs);
}
main file:
#include <iostream>
#include "Fraction.h"
int main() {
Fraction a{1,2}, b{3,4};
std::cout << a << " + " << b << " = " << a + b << "\n";
std::cout << a << " - " << b << " = " << a - b << "\n";
std::cout << a << " * " << b << " = " << a * b << "\n";
std::cout << a << " / " << b << " = " << a / b << "\n";
std::cout << a << " == " << b << " = " << (a == b) << "\n";
std::cout << a << " < " << b << " = " << (a < b) << "\n";
std::cout << a << " > " << b << " = " << (a > b) << "\n";
std::cout << a << " != " << b << " = " << (a != b) << "\n";
std::cout << a << " <= " << b << " = " << (a <= b) << "\n";
std::cout << a << " >= " << b << " = " << (a >= b) << "\n";
Fraction c(a);
std::cout << "c: " << c << "\n";
Fraction d{};
std::cout << "d: " << d << "\n";
d = b;
std::cout << "d: " << d << "\n";
return 0;
}
makefile:
CXX=g++
CXXFLAGS = -c -std=c++11 -Wall -Wpedantic
fraction : main.o Fraction.o
$(CXX) -o fraction main.o Fraction.o
main.o : main.cpp
$(CXX) $(CXXFLAGS) main.cpp
Fraction.o : Fraction.cpp Fraction.h
$(CXX) $(CXXFLAGS) Fraction.cpp Fraction.h
clean:
rm fraction main.o Fraction.o Fraction.h.gch
std::ratio
,std::ratio_add
,std::gcd
, etc. It does all this in compile time. That doesn't necessarily detract from the want to do this in runtime though. \$\endgroup\$get/set
methods. Make your free floating functions part of the public interface and let them access the object or use the constructor to set up the object correctly. \$\endgroup\$idiomatic Modern C++
could such a thing even be possible? XD \$\endgroup\$