본문 바로가기

개발 공부/C++

따라하며 배우는 C++ 9. 연산자 오버로딩

따라하며 배우는 C++ 

9. 연산자 오버로딩

 

 

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

 

#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 add(const Cents& c1, const Cents& c2)
{
	return Cents(c1.getCents() + c2.getCents());
}

int main() 
{
	Cents cents1(6);
	Cents cents2(8);
	Cents sum = add(cents1, cents2);
	cout << sum.getCents() << endl;
	return 0;
}
#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& c1, const Cents& c2)
{
	return Cents(c1.getCents() + c2.getCents());
}

int main() 
{
	Cents cents1(6);
	Cents cents2(8);
	cout << (cents1 + cents2 + Cents(6)).getCents() << endl;
	return 0;
}

이렇게 하여 산술 연산자도 오버로딩 가능!

 

#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; }

	friend Cents operator + (const Cents& c1, const Cents& c2);
    {
	return Cents(c1.getCents() + c2.getCents());
	}
};


int main() 
{
	Cents cents1(6);
	Cents cents2(8);
	cout << (cents1 + cents2 + Cents(6) + Cents(10)).getCents() << endl;
	return 0;
}

//여기서 .(멤버 선택), .* (멤버 포인터 선택), sizeof, ::, 삼항 연산자는 오버로딩이 안됨.

//연산자의 우선순위는 바꾸지 못한다.

bitwise XOR도 애매하기 때문에 오버로딩 안하는 편이 좋다.

 

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);
    {
	return Cents(this->m_cents + c2.m_cents);
	}
};

멤버 함수로 바꾸면 위와 같이 해야 한다.

 

멤버로만 해야 하는 연산자도 있다. =, [...]//어레이, (), ->

 

 

9.2 입출력 연산자 오버로딩 하기

 

#include <iostream>
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)
	{ }
	double getX() { return m_x; }
	double getY() { return m_y; }
	double getZ() { return m_z; }

	void print()
	{
		cout << m_x << " " << m_y << " " << m_z << endl;
	}
};

int main() 
{
	Point p1(0.0, 0.1, 0.2), p2(3.4, 1.5, 2.0);
	p1.print();
	cout << " ";
	p2.print();
	cout << endl;
	return 0;
}
#include <iostream>
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)
	{ }
	double getX() { return m_x; }
	double getY() { return m_y; }
	double getZ() { return m_z; }

	//이건 멤버 function으로 만들 수 없다.
	//첫번째 매개변수가 Point가 아닌 ostream이라서 그러하다.
	friend ostream& operator << (ostream& out, const Point& point)
	{
		out << point.m_x << " " << point.m_y << " " << point.m_z;
		return out;
	}
};

int main() 
{
	Point p1(0.0, 0.1, 0.2), p2(3.4, 1.5, 2.0);
	cout << p1 << endl;
	return 0;
}

#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)
	{ }
	double getX() { return m_x; }
	double getY() { return m_y; }
	double getZ() { return m_z; }

	//이건 멤버 function으로 만들 수 없다.
	//첫번째 매개변수가 Point가 아닌 ostream이라서 그러하다.
	friend ostream& operator << (ostream& out, const Point& point)
	{
		out << point.m_x << " " << point.m_y << " " << point.m_z;
		return out;
	}
};

int main() 
{
	ofstream of("out.txt");
	Point p1(0.0, 0.1, 0.2), p2(3.4, 1.5, 2.0);
	cout << p1 << endl;

	of << p1 << endl;
	of.close();
	return 0;
}

>> 하나로 파일에도 출력이 가능하다.

 

	friend istream& operator >> (istream& in, Point& point)
	{
		in >> point.m_x >> point.m_y >> point.m_z;
		return in;
	}

입력은 이렇게 구현 가능

 

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

 

#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)
	{
		return Cents(this->m_cents + c2.m_cents);
	}

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

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

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

int main() 
{
	Cents cents1(6);
	Cents cents2(0);
	cout << cents1 << endl;
	cout << -cents1 << endl;
	cout << -Cents(-10) << endl;

	cout << !cents1 << " " << cents2 << endl;
	return 0;
}

 

9.4 비교 연산자 오버로딩 하기.

 

sort를 하기 위해선 꼭 오버로딩 되어 있어야 한다!

 

#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; }

	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;
	}

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

int main() 
{
	Cents ct1(6);
	Cents ct2(6);
	if (ct1 == ct2) cout << "Euqal!" << endl;
	return 0;
}

 

#include <iostream>
#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;
	}
};

int main() 
{
	vector<Cents> arr(20);
	for (unsigned i = 0; i < 20; ++i)
		arr[i].getCents() = i;
	random_shuffle(begin(arr), end(arr));
	for (auto& e : arr) cout << e << " ";
	cout << endl;
	sort(begin(arr), end(arr));
	return 0;
}

 

 

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

 

#include <iostream>
#include <vector>
#include <algorithm>

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)
	{
		Digit temp(m_digit);
		++(*this);
		return temp;
	}
	friend ostream& operator<<(ostream& out, const Digit& d)
	{
		out << d.m_digit;
		return out;
	}
};

int main() 
{
	Digit d(5);
	cout << ++d << endl;
	cout << d << endl;
	cout << d++ << endl;
	cout << d << endl;
	return 0;
}

9.6 첨자 연산자 오버로딩 하기

[] subscript operator

 

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class IntList
{
private:
	int m_list[10];
public:
	void setItem(int index, int value)
	{
		m_list[index] = value;
	}

	int getItem(int index)
	{
		return m_list[index];
	}
};

int main() 
{
	IntList my_list;
	my_list.setItem(3, 1);
	cout << my_list.getItem(3) << endl;
	return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class IntList
{
private:
	int m_list[10];
public:
	void setItem(int index, int value)
	{
		m_list[index] = value;
	}

	int getItem(int index)
	{
		return m_list[index];
	}

	int* getList()
	{
		return m_list;
	}
};

int main() 
{
	IntList my_list;
	my_list.setItem(3, 1);
	cout << my_list.getItem(3) << endl;
	my_list.getList()[3] = 1;
	cout << my_list.getList()[3] << endl;
	return 0;
}

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class IntList
{
private:
	int m_list[10];
public:
	int& operator[] (const int index)
	{
		return m_list[index];
	}
};

int main() 
{
	IntList my_list;
	my_list[3] = 10;
	cout << my_list[3] << endl;
	return 0;
}

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class IntList
{
private:
	int m_list[10];
public:
	int& operator[] (const int index)
	{
		return m_list[index];
	}

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

int main() 
{
	const IntList my_list;
	//my_list[3] = 10;
	cout << my_list[3] << endl;
	return 0;
}

위는 const일 경우

 

#include <iostream>
#include <cassert>

using namespace std;

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

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

assert로 오류를 미연에 방지할 수 있다.

 

#include <iostream>
#include <cassert>

using namespace std;

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

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

int main() 
{
	IntList* list = new IntList;
	(*list)[3] = 10;
	list[3] = IntList; //의도가 달라진다. 
	return 0;
}

 

9.7 괄호 연산자 오버로딩과 함수 객체

parenthesis, Function object(Functor)

 

 

#include <iostream>
using namespace std;

class Accumulator
{
private:
	int m_counter = 0;
public:
	int operator() (int i) { return (m_counter += i); }
};
int main() 
{ 
	Accumulator acc;
	cout << acc(10) << endl;
	cout << acc(20) << endl;
	return 0;
}

이런 식으로 함수처럼 호출하는 것 자체를 Functor라고 부른다.

 

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;
	}
};

void printInt(const int& value)
{
	cout << value << endl;
}
int main() 
{ 
	Cents cents(7);
	printInt(cents);
	return 0;
}

 

#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;
	}
};

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

class Dollar
{
private:
	int m_dollars = 0;
public:
	Dollar(const int& input) : m_dollars(input) {}
	operator Cents()
	{
		return Cents(m_dollars *100);
	}
};
int main() 
{ 
	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_denominator), m_denominator(fraction.m_denominator)
	{
		cout << "Copy constroctor called" << endl;
	}
	friend ostream& operator << (ostream& out, const Fraction& f)
	{
		out << f.m_numerator << "/" << f.m_denominator << endl;
		return out;
	}
};
int main() 
{ 
	Fraction frac(3, 5);
	Fraction fr_copy(frac); //Fraction fr_copy = frac의 경우도 가능
	cout << frac << " " << fr_copy << endl;
	return 0;
}

 

//가끔 보안을 중요시하는 경우 copy constructor를 private에 넣기도 한다.

//Fraction fr_copy(Fraction(3, 10))의 경우 최적화를 통해 복사 생성자를 없애기도 한다.

 

#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_denominator), m_denominator(fraction.m_denominator)
	{
		cout << "Copy constroctor called" << endl;
	}
	friend ostream& operator << (ostream& out, const Fraction& f)
	{
		out << f.m_numerator << "/" << f.m_denominator << endl;
		return out;
	}
};

Fraction doSomething()
{
	Fraction temp(1, 2);
	return temp;
}
int main() 
{ 
	Fraction result = doSomething();
	cout << result << endl;
	return 0;
}

위의 케이스에서 release 모드에선 복사 생성자가 생기지 않는다.

함수를 벗어나면 해당 return 값이 의미가 없어지기 때문에 컴파일러가 알아서 변수에 담아버린다.

 

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

 

#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_denominator), m_denominator(fraction.m_denominator)
	{
		cout << "Copy constroctor called" << endl;
	}
	friend ostream& operator << (ostream& out, const Fraction& f)
	{
		out << f.m_numerator << "/" << f.m_denominator << endl;
		return out;
	}
};

void doSomething(Fraction frac)
{
	cout << frac << endl;
}
int main() 
{ 
	Fraction frac(7);
	doSomething(7); //이것도 가능.
	//어떤 값이 들어왔을 때 매개변수에서 형 변환을 할 것이 정해져 있다면
	//알아서 생성자의 인수로 넣어주는 것. = 변환 생성자.


	return 0;
}

그러나 생성자 앞에 "explicit" 키워드를 쓰면 형 변환을 막아버린다.

explicit Fraction(int num = 0, int den = 1)

//

Fraction frac(10);

doSomething(frac);

 

#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)
		: m_numerator(num), m_denominator(den)
	{
		assert(den != 0);
	}

	Fraction(const Fraction& fraction) //copy constructor
		: m_numerator(fraction.m_denominator), m_denominator(fraction.m_denominator)
	{
		cout << "Copy constroctor called" << endl;
	}
	friend ostream& operator << (ostream& out, const Fraction& f)
	{
		out << f.m_numerator << "/" << f.m_denominator << endl;
		return out;
	}
};

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

	//Fraction frac2('a'); //불가
	return 0;
}

9.11 대입 연산자 오버로딩, 깊은 복사, 얕은 복사. (Deep copy vs Shallow copy)

 

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

class MyString
{
public:
	char* m_data = nullptr;
	int m_length = 0;
public:
	MyString(const char* source = "")
	{
		assert(source);
		m_length = strlen(source) + 1; //글자 수 + null 캐릭터
		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()
	{
		delete[] m_data;
	}
	int getLength() { return m_length; }
	char* getString() { return m_data; }
};
int main() 
{ 
	MyString hello("Hello");
	cout << (int*)hello.m_data << endl;
	cout << hello.getString() << endl;

	{
		MyString copy = hello;  //복사 생성자가 호출됨.
        //복사 생성자가 hello의 주소를 복사하여 copy에 넣어줌.
        //즉 copy에는 hello의 주소가 복사되기만 한 것이기 때문에
        //"두" MyString의 m_data에 대해 할당된 메모리가 "하나"인 것이다.
		cout << (int*)copy.m_data << endl;
		cout << copy.getString() << endl;
        //소멸자가 있으므로 scope를 벗어나면 copy의 메모리가 해제된다.
        //그 과정에서 같은 곳을 가리키고 있었던 hello의 메모리도 강제로 해제되어 버리는 것.
	}
	cout << hello.getString() << endl;
	return 0;
}

 

포인터에서도 주소 값만 복사해 넣어주는 것을 얕은 복사라고 한다(Shallow Copy)

그리고 주소 값을 복사하는 대신 메모리를 다시 할당 받고, 값을 하나하나 복사받는 것을 깊은 복사라고 한다.

 

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

class MyString
{
public:
	char* m_data = nullptr;
	int m_length = 0;
public:
	MyString(const char* source = "")
	{
		assert(source);
		m_length = strlen(source) + 1; //글자 수 + null 캐릭터
		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';
	}

	//copy constructor
	MyString(const MyString &source)
	{
		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;
	}
	//= operator 오버로딩

	MyString& operator = (const MyString& source)
	{
		//default copy constructor는 아래 주석과 같은 내용으로 되어 있다.
		//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; //원래 있던 string을 지워버리고
		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;

	}
	//desctuctor
	~MyString()
	{
		delete[] m_data;
	}
	int getLength() { return m_length; }
	char* getString() { return m_data; }
};
int main() 
{ 
	MyString hello("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;
}

 

int main() 
{ 
	MyString hello("hello");
	MyString str = hello; //이 경우 str이 새로 생성되는 객체지만
	//copy constructor가 호출됨.
	MyString str2;
	str2 = hello; //이럴 경우 assignment
	return 0;
}

 

//얕은 복사를 막기 위해 차선책은 그냥 막아버리는 것.
//물론 깊은 복사를 구체적으로 구현하는 것이 제일 좋다
MyString(const MyString &source) = delete;

 

 

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 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];*/
		//이니셜라이저 리스트는 []를 제공하지 않는다.
	}
	~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() 
{ 
	int my_arr1[5] = { 1, 2, 3, 4, 5 }; //기본 자료형은 이렇게 초기화 리스트를
	//편하게 사용할 수 있다. 정적이건(위) 동적이건(아래)
	int* my_arr = new int[5]{ 1,2, 3, 4,5 };
	auto il = { 10, 20, 30 }; //initializer list를 인클루드하면
							//자동으로 형을 잡아준다.
	IntArray int_array{ 1, 2, 3, 4, 5 };
	cout << int_array << endl;
	IntArray int_array2 = { 1, 2, 3, 4, 5 };
	cout << int_array2 << endl;
	return 0;
}