일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- new&delete
- constructor
- delete function
- vector size
- suffix return type
- C++
- conversion constructor
- virtual function
- increment operator
- std::ostream
- operator overloading
- c++ basic practice
- diamond inheritance
- virtual function table
- 더 지니어스 양면포커
- c++ multi chatting room
- this call
- pointer to member data
- member function pointer
- std::endl
- discord bot
- std::cout
- placement new
- std::vector
- dynamic_cast
- virtual inheritance
- return by reference
- vector capacity
- base from member
- virtual destructor
- Today
- Total
I'm FanJae.
[C++] Operator Overloading III 본문
※ 본 포스트는 코드누리 C++ Basic 강의 내용을 보고 정리한 포스트입니다.
1. std::cout의 원리
#include <iostream>
int main()
{
std::cout << 10;
}
1-1. std::cout의 원리
- std::ostream 타입의 객체이다.
- operator<< 연산자 재정의 기술을 사용하여 재정의 한 것이다.
#include <iostream>
/*
namespace std
{
class ostream
{
};
ostream cout;
}*/
int main()
{
std::cout << 10; // cout.operator<<(10)
std::cout.operator<<(10);
}
- 즉, 실제로는 std namespace안에 ostream이라는 class가 존재한다는 것이다.
- 또한 std::cout은 std::cout.operator<<(10); 과 같이 호출이 가능하다.
※ 정확히 말하면, basic_ostream 이라는 클래스 템플릿이다.
template< typename CharT, typename Traits = std::char_traits<CharT>>
class basic_ostream : virtual public std::basic_ios<charT, Traits>
using ostream = basic_ostream<char, char_traits<char>>;
- 이와 같이 처리되어 있는 것이다.
1-2. std::ostream과 유사한 클래스 만들기
- 연산자 재정의 문법을 이해 하기 위해서 사용하는 것으로 화면 출력은 printf를 사용할 것임.
- 실제 basic_ostream의 구현은 출력 버퍼 등을 사용해 상당히 복잡하다고 한다.
#include <stdio.h>
namespace std // 학습 용도
{
class ostream
{
};
ostream cout;
}
int main()
{
std::cout << 10;
}
- 이 예제는 학습의 용도일 뿐이다. 절대 std에 임의로 뭔가 추가해서 쓰면 안된다.
1-2-1. operator<<() 연산자 함수 제공
#include <stdio.h>
namespace std
{
class ostream
{
public:
void operator<<(int arg) { printf("%d",arg); }
void operator<<(char arg) { printf("%c",arg); }
void operator<<(const char* arg) { printf("%s",arg); }
void operator<<(void* arg) { printf("%p",arg); }
};
ostream cout;
}
int main()
{
std::cout << 10;
}
- 모든 표준 타입에 대해서 제공이 가능해야 한다.
- 한편 cout은 한번만 출력하지 않는다. 아래 처럼 여러번 출력도 가능하게 해야한다.
std::cout << 10 << '\t' << 20 << '\n';
- 우린 이러한 상황을 여러번 시도한적이 있다.
1-2-2. << 을 사용한 연속적인 출력
#include <stdio.h>
namespace std
{
class ostream
{
public:
ostream& operator<<(int arg) { printf("%d",arg); return *this; }
ostream& operator<<(char arg) { printf("%c",arg); return *this; }
ostream& operator<<(const char* arg) { printf("%s",arg); return *this; }
ostream& operator<<(void* arg) { printf("%p",arg); return *this; }
};
ostream cout;
}
int main()
{
std::cout << 10 << '\t' << 20 << '\n';
}
- 이와 같이 operator<<() 함수가 자기 자신을 참조로 반환하도록 해야한다.
- 또한, operator<<() 함수가 non-const member function임에 유의해야 한다.
1-2-3. std::cout을 상수 참조로 가리키면 발생하는 문제점
#include <iostream>
void f1(std::ostream& os)
{
os << "hello\n";
}
void f2(const std::ostream& os)
{
os << "hello\n"; // os.operator<<(...);
}
int main()
{
f1(std::cout);
f2(std::cout);
}
- const를 받지 못하는 이유는 함수 내부적으로 출력 버퍼 등의 상태를 변경하기 때문에, const 값을 받을 수 없다.
- 따라서, std::cout을 상수 참조로 가리키면 operator<<() 함수 사용이 불가능하다.
- std::cout을 함수 인자로 받을 때는 non-const reference를 사용해야한다.
2.std::endl의 정체
#include <stdio.h>
namespace std
{
class ostream
{
public:
ostream& operator<<(int arg) { printf("%d",arg); return *this; }
ostream& operator<<(char arg) { printf("%c",arg); return *this; }
ostream& operator<<(const char* arg) { printf("%s",arg); return *this; }
ostream& operator<<(void* arg) { printf("%p",arg); return *this; }
};
ostream cout;
}
int main()
{
std::endl ( std::cout );
std::cout << std::endl; // 위 2개다 문제되지 않는다.
}
- std::endl는 함수이다.
- 따라서, 위처럼 인자값을 받는 행위가 아무런 문제가 되지 않는다.
- 이것 역시, std namespace에 구현될 것이기 아래와 같이 추가할 수 있다.
2-1. std::endl의 구현
namespace std
{
class ostream
{
public:
ostream& operator<<(int arg) { printf("%d",arg); return *this; }
ostream& operator<<(char arg) { printf("%c",arg); return *this; }
ostream& operator<<(const char* arg) { printf("%s",arg); return *this; }
ostream& operator<<(void* arg) { printf("%p",arg); return *this; }
};
ostream cout;
ostream& endl(ostream& os)
{
os << '\n';
return os;
}
}
int main()
{
std::endl ( std::cout );
// std::cout << std::endl;
}
- 구현하면, std::cout << std::endl;은 아직 작동하지 않는다.
- 하지만 이것 역시도 구현이 가능하다.
- std::endl;은 함수다. 즉, 이것은 cout.operator<<(endl); endl의 주소 값을 받아오면 된다. (함수 포인터로 받아서)
2-2. 함수 포인터를 추가한 std::endl의 구현
namespace std
{
class ostream
{
public:
ostream& operator<<(int arg) { printf("%d",arg); return *this; }
ostream& operator<<(char arg) { printf("%c",arg); return *this; }
ostream& operator<<(const char* arg) { printf("%s",arg); return *this; }
ostream& operator<<(void* arg) { printf("%p",arg); return *this; }
};
ostream cout;
ostream& endl(ostream& os)
{
os << '\n';
return os;
}
ostream& operator<<(ostream&)(*f)(ostream&))
{
f(*this); // endl(cout)
return *this;
}
}
int main()
{
std::endl ( std::cout );
// std::cout << std::endl;
}
- operator<<(함수 포인터) 로 전달 받아서 다시 endl 함수를 호출하는 원리이다.
- std::endl, std::hex, std::dec 등도 모두 함수이다.
- 이들을 보통 입출력 조정자 함수라고 칭하는데 사용자가 새로운 조정자 함수를 추가하는 것도 가능하다.
namespace std
{
class ostream
{
public:
ostream& operator<<(int arg) { printf("%d",arg); return *this; }
ostream& operator<<(char arg) { printf("%c",arg); return *this; }
ostream& operator<<(const char* arg) { printf("%s",arg); return *this; }
ostream& operator<<(void* arg) { printf("%p",arg); return *this; }
};
ostream cout;
ostream& endl(ostream& os)
{
os << '\n';
return os;
}
ostream& operator<<(ostream&)(*f)(ostream&))
{
f(*this); // endl(cout) // tab(cout)
return *this;
}
}
std::ostream& tab(std::ostream& os)
{
os << '\t';
return os;
}
std::ostream& menu(std::ostream& os)
{
os << "1. AAA\n";
os << "2. BBB\n";
return 0s;
}
int main()
{
std::endl ( std::cout );
std::cout << std::endl; // cout.operator<<(endl);
std::cout << "A" << tab << "B" << std::endl;
std::cout << menu;
}
- 이와 같이 원리를 이해하고 있다면, tab, menu와 같은 것도 추가할 수 있다.
- 성능의 경우는 이 때문에 std::endl을 사용하는 것 보다 '\n'을 사용하는 것이 빠르다.
2-3. 결합도 가능한가?
#include <iostream>
std::ostream& tab(std::ostream& os)
{
os << '\t';
return os;
}
std::ostream& menu(std::ostream& os)
{
os << "1. AAA\n";
os << "2. BBB\n";
return 0s;
}
int main()
{
std::cout << menu; // 표준 C++ Cout과 우리가 만든 메뉴를 섞어쓰고 있다.
}
- 표준과도 잘 연동되는 것을 확인할 수 있다.
- std::cout이 성능이 떨어진다고 하는 이유가 이러한 디자인 때문도 있다.
3. user define type & std::cout
#include <iostream>
class Point
{
int x{0};
int y{0};
public:
Point(int x, int y) : x{x}, y{y} {}
};
int main()
{
Point p{1,2};
std::cout << p; // error.
std::cout << 1; // cout.operator<<(int)
std::cout << 3.4; // cout.operator<<(double)
}
- std::cout은 Point를 어떻게 출력해야할지 몰라서 오류가 발생한다.
- 다만, cout.operator<<(Point) 처럼 멤버로 추가는 불가능하다.
※ std::cout으로 사용자 정의 타입(Point)의 객체를 출력하려면, operator<<(std::ostream, Point) 함수 제공이 필요하다.
3-1. 사용자 정의 타입의 출력
std::ostream& operator<<(std::ostream& os, const Point& pt)
{
return os << pt.x << ", " << pt.y << std::endl;
}
- 이와 같이 출력처리 하면, 사용자 정의 타입의 객체도 출력할 수 있다.
#include <iostream>
class Point
{
int x{0};
int y{0};
public:
Point(int x, int y) : x{x}, y{y} {}
friend std::ostream& operator<<(std::ostream& os, const Point& pt);
};
std::ostream& operator<<(std::ostream& os, const Point& pt)
{
return os << pt.x << ", " << pt.y << std::endl;
}
int main()
{
Point p{1,2};
std::cout << p;
std::cout << 1; // cout.operator<<(int)
std::cout << 3.4; // cout.operator<<(double)
}
- 이와 같이 구현하면, std::cout에서도 사용자 정의 객체를 잘 출력해준다.
- private인 멤버에 접근 하기 위해서는 getter또는 friend 함수로 만들어 주면 된다.
3-1-1. 구현된 내용 추가 설명.
std::ostream& operator<<(std::ostream& os, const Point& pt)
{
return os << pt.x << ", " << pt.y << std::endl;
}
- 위 문장에서 std::ostream& os는 const가 아님에 반해, const Point& pt는 const이다.
① Point& pt에는 const를 붙이는 이유
const Point p{1,2};
std::cout p; // 상수 객체도 출력은 가능해야한다.
- 아무리 상수 객체라도 출력은 가능해야 한다.
- 상수 객체를 받아주기 위해서는 인자 값을 const로 받아야 한다.
② std::ostream& os가 const가 없는 이유
- 이 내용은 앞서 다뤘듯 operator<< 는 non-const member function이다.
- 실제 std::ostream에 구현되어 있는 operator<< 는 출력 버퍼등의 상태가 변환된다.
- 따라서, const를 붙이면 출력 자체가 불가능해진다.
3-2. User define type(Point)가 특정 이름 공간 안에 있는 경우
#include <iostream>
namespace Graphics
{
class Point
{
int x{0};
int y{0};
public:
Point(int x, int y) : x{x}, y{y} {}
friend std::ostream& operator<<(std::ostream& os, const Point& pt);
};
std::ostream& operator<<(std::ostream& os, const Point& pt)
{
return os << pt.x << ", " << pt.y << std::endl;
}
}
int main()
{
Graphics::Point p{1,2};
std::cout << p; // ? // operator<<(cout, p)
}
- std::cout 입장에서는 Graphics라는 정보가 전혀 없지만, 이 경우에도 실행되는데 전혀 문제가 없다.
- 이것이 가능한 이유는 'ADL' 이라는 것 때문에 가능하다고 한다.
- ADL에 대해서는 별도로 포스트를 다뤄보고자 한다.
'C++ > Basic' 카테고리의 다른 글
[C++] STL Container I (0) | 2024.08.27 |
---|---|
[C++] Operator Overloading IV (2) | 2024.08.26 |
[C++] Operator Overloading II (0) | 2024.08.24 |
[C++] Operator Overloading I (0) | 2024.08.23 |
[C++] Multiple Inheritance, Diamond Inheritance, Virtual Inheritance (0) | 2024.08.23 |