[따배C++] 9. 연산자 오버로딩

Created: June 6, 2021 11:26 AM
Tag: RVO, converting constructor, copy constructor, deep copy, explicit, operator overloading, satic_cast typecasts, shallow copy

9.1 산술 연산자 오버로딩 하기


  • 오버로딩이 불가능한 연산자: ? : :: sizeof . .*
  • 연산자의 우선순위는 오버로딩 하지 못한다.
  • 직관적으로 받아들일 수 있는 경우로만 사용해야 한다. 애메할 때는 함수를 사용하자.
  • ^ 연산자는 우선순위가 매우 낮으므로 오버로딩 하는것을 지양해야 한다. (사용하고자 한다면 괄호로 싸주어야 한다.)
  • =, [], (), ->은 member function으로만 구현할 수 있다.
#include <iostream>

using namespace std;

class Cents
{
private:
	int m_cents;

public:
	Cents(int cents = 0) { m_cents = cents; }

	int getCents() const { return m_cents; }

	int &getCents() { return m_cents; }

	Cents operator + (const Cents &c2) // member function으로 구현
	{
		return (Cents(this->m_cents + c2.m_cents));
	}
};

int main(void)
{
	Cents cents1(6);
	Cents cents2(8);

	cout << (cents1 + cents2 + Cents(6) + Cents(10)).getCents() << endl;
}

9.2 입출력 연산자 오버로딩 하기(파일 입출력)


#include <iostream>
#include <fstream>
using namespace std;

class Point
{
private:
	double m_x, m_y, m_z;

public:
	Point(double x = 0.0, double y = 0.0, double z = 0.0)
		: m_x(x), m_y(y), m_z(z)
	{}

	friend std::ostream &operator << (std::ostream &out, const Point &point)
	{
		out << "( " << point.m_x << " " << point.m_y << " " << point.m_z << " )";
		return (out); // for chaining
	}
};

int main(void)
{
	ofstream of("out.txt");	// output file stream

	Point p1(0.0, 0.1, 0.2), p2(3.4, 1.5, 2.0);

	cout << p1 << " " << p2 << endl;
	of << p1 << " " << p2 << endl;

	of.close();

	return (0);
}

9.3 단항 연산자 - ! 오버로딩 하기


#include <iostream>
#include <fstream>
using namespace std;

class Cents
{
private:
	int m_cents;

public:
	Cents(int cents = 0) { m_cents = cents; }

	int getCents() const { return m_cents; }

	int &getCents() { return m_cents; }

	Cents operator - () const
	{
		return Cents(-m_cents);
	}

	bool operator ! () const // <-
	{
		return ((m_cents == 0) ? true : false);
	}

	friend std::ostream &operator << (std::ostream &out, const Cents &cents)
	{
		out << cents.m_cents;
		return (out);
	}
};

int main(void)
{
	Cents cents1(6);
	Cents cents2(0);

	cout << cents1 << endl;
	cout << -cents1 << endl;
	cout << -Cents(-10) << endl;

	cout << !cents1 << " " << !cents2 << endl;

	return (0);
}

9.4 비교 연산자 오버로딩 하기 ==, !=, >, >=


#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
using namespace std;

class Cents
{
private:
	int m_cents;

public:
	Cents(int cents = 0) { m_cents = cents; }
	int getCents() const { return m_cents; }
	int &getCents() { return m_cents; }

	friend bool operator == (const Cents &c1, const Cents &c2)
	{
		return (c1.m_cents == c2.m_cents);
	}

	friend std::ostream &operator << (std::ostream &out, const Cents &cents)
	{
		out << cents.m_cents;
		return (out); // for chaining
	}
};

int main(void)
{
	Cents cents1(6);
	Cents cents2(6);

	if (cents1 == cents2)
		cout << "equal" << endl;

	cout << std::boolalpha;

	return (0);
}
#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
using namespace std;

class Cents
{
private:
	int m_cents;

public:
	Cents(int cents = 0) { m_cents = cents; }
	int getCents() const { return m_cents; }
	int &getCents() { return m_cents; }

	friend bool operator == (const Cents &c1, const Cents &c2)
	{
		return (c1.m_cents == c2.m_cents);
	}

	friend bool operator < (const Cents &c1, const Cents &c2)
	// 반드시 less than으로 구현되어야 한다!
	{
		return (c1.m_cents <  c2.m_cents);
	}

	friend std::ostream &operator << (std::ostream &out, const Cents &cents)
	{
		out << cents.m_cents;
		return (out);
	}
};

int main(void)
{
	vector<Cents> arr(20);
	for (unsigned i = 0; i < 20; ++i)
		arr[i].getCents() = i;

	std::random_shuffle(begin(arr), end(arr));

	for (auto &e : arr)
		cout << e << " ";
	cout << endl;

	// cents들 끼리 크기비교 불가능 -> 연산자 오버로딩
	std::sort(begin(arr), end(arr));

	for (auto &e : arr)
		cout << e << " ";
	cout << endl;

	return (0);
}

9.5 증감 연산자 오버로딩 하기


#include <iostream>
using namespace std;

class Digit
{
private:
	int m_digit;

public:
	Digit(int digit = 0) : m_digit(digit) {}

	// prefix
	Digit &operator ++ ()
	{
		++m_digit;
		return (*this);  // 자기 자신을 반환
	}

	// postfix
	Digit operator ++ (int) // 여기서 int는 dummy로 사용된다
	{
		Digit temp(m_digit);
		++(*this);
		return (temp);
	}

	friend ostream &operator << (ostream &out, const Digit &d)
	{
		out << d.m_digit;
		return (out);
	}
};

int main(void)
{
	Digit d(5);

	cout << ++d << endl;
	cout << d << endl;

	cout << d++ << endl;
	cout << d << endl;

	return (0);
}

9.6 첨자 연산자 오버로딩 하기 [] subscript operator


#include <iostream>
#include <cassert>
using namespace std;

class IntList
{
private:
	int m_list[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

public:
	int &operator [] (const int index)
	{
		assert(index >= 0);
		assert(index < 10);
		return (m_list[index]);
	}

	const int &operator [] (const int index) const
	{
		return (m_list[index]);
	}
};

int main(void)
{
	const IntList my_list;
	cout << my_list[3] << endl;

	IntList *list = new IntList;

	list[3] = 10; // not ok
	(*list)[3] = 10; // ok

	return (0);
}
💡 `assert`를 사용하는것이 조건문을 사용하는 것보다 퍼포먼스에 유리하다.

9.7 괄호 연산자 function object, functer


마치 함수인것 처럼 사용할 수 있다. (Functor)

#include <iostream>
using namespace std;

class Accumulator
{
private:
	int m_counter = 0;

public:
	int operator()(int i) { return (m_counter += i); }
};

int main(void)
{
	Accumulator acc;
	cout << acc(10) << endl;
	cout << acc(20) << endl;

	return (0);
}

9.8 형변환을 오버로딩 하기 static_cast typecasts


#include <iostream>
using namespace std;

class Cents
{
private:
	int m_cents;

public:
	Cents(int cents = 0)
	{
		m_cents = cents;
	}

	int getCents() { return (m_cents); }

	void setCents(int cents) { m_cents = cents; }

	operator int()
	{
		cout << "cast here" << endl;
		return m_cents;
	}
};

class Dollar
{
private:
	int m_dollars = 0;

public:
	Dollar(const int &input) : m_dollars(input) {}

	operator Cents()
	{
		return (Cents(m_dollars * 100));
	}
};

void printInt(const int &value)
{
	cout << value << endl;
}

int main(void)
{
	Dollar dol(2);

	Cents cents = dol;

	printInt(cents);

	return (0);
}

9.9 복사 생성자, 복사 초기화, 반환값최적화 RVO return value optimization


#include <iostream>
#include <cassert>
using namespace std;

class Fraction
{
private:
	int m_numerator;
	int m_denominator;

public:
	Fraction(int num = 0, int den = 1)
		: m_numerator(num), m_denominator(den)
	{
		assert(den != 0);
	}

	Fraction(const Fraction &fraction) // copy constructor // 자신과 똑같은 타입의 인스턴스가 인풋으로 들어옴
		: m_numerator(fraction.m_numerator), m_denominator(fraction.m_denominator)
	{
		cout << "copy constructor called" << endl;
	}

	friend std::ostream &operator << (std::ostream &out, const Fraction &f)
	{
		out << f.m_numerator << " / " << f.m_denominator << endl;
		return (out);
	}
};

Fraction doSomething()
{
	Fraction temp(1, 2);

	cout << &temp << endl;

	return (temp);
}

int main(void)
{
	Fraction frac(3, 5);

	Fraction fr_copy = frac;

	cout << frac << " " << fr_copy << endl;

// ============================================================
// RVO by compiler

	Fraction result = doSomething();
	cout << &result << endl;
	cout << result << endl;

	return (0);
}

9.10 변환 생성자 converting constructor, explicit, delete


#include <iostream>
#include <cassert>
using namespace std;

class Fraction
{
private:
	int m_numerator;
	int m_denominator;

public:
	Fraction(char) = delete; // 파라미터 금지

	Fraction(int num = 0, int den = 1)
	// explicit Fraction(int num = 0, int den = 1)
		: m_numerator(num), m_denominator(den)
	{
		assert(den != 0);
	}

	Fraction(const Fraction &fraction) // copy constructor
		: m_numerator(fraction.m_numerator), m_denominator(fraction.m_denominator)
	{
		cout << "copy constructor called" << endl;
	}

	friend std::ostream &operator << (std::ostream &out, const Fraction &f)
	{
		out << f.m_numerator << " / " << f.m_denominator << endl;
		return (out);
	}
};

void doSomething(Fraction frac)
{
	cout << frac << endl;
}

int main(void)
{
	Fraction frac(7);

	doSomething(frac);
	doSomething(7);

	return (0);
}

9.12 이니셜라이져 리스트 initializer list


이니셜라이져 리스트는 클래스와 같은 사용자 정의 자료형에서 생성자나 대입 연산자를 만들 때 편리하게 사용할 수 있다.

#include <iostream>
#include <cassert>
#include <initializer_list>

using namespace std;

class IntArray
{
private:
	unsigned m_length = 0;
	int *m_data = nullptr;

public:
	IntArray(unsigned length)
		: m_length(length)
	{
		m_data = new int[length];
	}

	IntArray(const std::initializer_list<int> &list)
		:IntArray(list.size())
	{
		int count = 0;
		for (auto &element : list)
		{
			m_data[count] = element;
			++count;
		}

		// for (unsigned count = 0; count < list.size(); ++count)
		// 	m_data[count] = list[count]; // error
	}

	~IntArray()
	{
		delete[] this->m_data;
	}

	friend ostream &operator << (ostream &out, IntArray &arr)
	{
		for (unsigned i = 0; i < arr.m_length; ++i)
			out << arr.m_data[i] << " ";
		out << endl;
		return (out);
	}
};

int main(void)
{
	int my_arr1[5] = { 1, 2, 3, 4, 5 }; // init list // 기본자료형
	int *my_arr2 = new int [5]{ 1 , 2, 3, 4, 5 }; // 동적할당

	auto il = { 10, 20, 30 }; // 자동으로 init list 임을 인식

	IntArray int_array { 1, 2, 3, 4, 5 };
	cout << int_array << endl;

	return (0);
}

9.11 대입 연산자 오버로딩, 깊은 복사 deep copy, 얕은 복사 shallow copy


동적할당된 메모리에 대한 포인터 변수를 맴버로서 가지고 있는 클래스에서는 복사, 대입에서 깊은/얕은 복사에 따라 대입 연산자 오버로딩이 까다로워 진다. 이러한 문제에 대해 직접 고민할 일은 없지만 이해하는것은 필요하다.

#include <cassert>
#include <iostream>
using namespace std;

class MyString
{
//private:
public:
	char *m_data = nullptr; // 얕은 복사
	int m_length = 0;

public:
	MyString(const char *source = "")
	{
		assert(source);

		m_length = std::strlen(source) + 1;
		m_data = new char[m_length];

		for (int i = 0; i < m_length; ++i)
			m_data[i] = source[i];

		m_data[m_length - 1] = '\0';
	}

	MyString(const MyString &source) // copy constructor
	{
		cout << "copy constructor " << endl;

		m_length = source.m_length;

		if (source.m_data != nullptr)  // 메모리를 다시 새로 할당받고 내용을 복사 == 깊은 복사
		{
			m_data = new char[m_length];

			for (int i = 0; i < m_length; ++i)
				m_data[i] = source.m_data[i];
		}
		else
			m_data = nullptr; // 0 대신 null ptr을 사용하는것이 최신문법
	}

	MyString &operator = (const MyString &source)
	{
		// shallow copy
		// this->m_data = source.m_data;
		// this->m_length = source.m_length;

		cout << "assignment operator " << endl;

		if (this == &source) // prevent self-assignment
			return (*this);

		delete[] m_data; // 이미 메모리를 가지고 있었을 수도 있음

		m_length = source.m_length;

		if (source.m_data != nullptr)
		{
			m_data = new char[m_length];

			for (int i = 0; i < m_length; ++i)
				m_data[i] = source.m_data[i];
		}
		else
			m_data = nullptr;

		return (*this);
	}

	~MyString()
	{
		delete[] m_data;
	}

	char *getString() { return (m_data); }
	int getLength() { return (m_length); }
};

int main(void)
{
	MyString hello("Hello");

	MyString str1(hello); // MyString str1 = hello;

	MyString str2;
	str2 = hello; // 할당 형태에 따라 다르다

	// cout << (int *)hello.m_data << endl;
	// cout << hello.getString() << endl;

	{
		MyString copy = hello; // 주소를 말그대로 복사만
		cout << (int *)copy.m_data << endl;
		cout << copy.getString() << endl;
	}

	cout << hello.getString() << endl;

	return (0);
}

좋은 웹페이지 즐겨찾기