앞의 vector 출력문을 함수로 구현해 보자.
void print( const std::vector< int >& some ) // '&' 참조기호를 사용한 이유는 스택에 복사하기 싫기 때문
{
for( auto one : some )
std::cout << one << std::endl;
}
간단하지만, int 타입의 vector 밖에 사용할 수 없으니 답답하다.
템플릿으로 일반화 하자.
template< class T >
void print( const std::vector< T >& some )
{
for( auto one : some )
std::cout << one << std::endl;
}
훌륭하다.
하지만 vector 뿐만 아니라 다른 컨테이너들의 경우는 일일이 만들어주어야 한다.
template< class T >
void print( const T& some )
{
for( auto one : some )
std::cout << one << std::endl;
}
더 간단하고 더 효과적으로 일반화가 되었다.
당연하게도 모든 컨테이너가 ranged for 나, std::cout 으로의 즉시 출력을 지원하지 않으니 만능은 아닐 것이다.
ranged_for 를 지원하기 위해서는 begin(), end() 를 비롯한 몇 가지 인터페이스를 구현해 주어야 한다.
사용자 클래스들인 경우는 std::cout 으로의 출력을 지원하는 인터페이스도 만들어줘야 한다.
여기서 더 일반화 할 꺼리가 무엇이 있을까?
지금은 std::cout 으로의 콘솔 출력만 지원하는 셈이다.
일반적인 output stream 에 모두 적용할 수 있도록 하고싶다면,
template< class T >
std::ostream& print( std::ostream& os, const T& some )
{
for( auto one : some )
os << one << std::endl;
return os;
}
처럼 구현하고 print( cout, some ); 처럼 호출해주면 된다.
return 이 등장한 이유는 print( print( print( .... 처럼 직렬 표기를 가능케 하기 때문.
template< class T >
auto& print( std::ostream& os, const T& some )
{
for( auto one : some )
os << one << std::endl;
return os;
}
요게 조금 더 깔끔하긴 하다.
급식시절 논리에 대해 조금이라도 귀담아 들었던 사람은
성급한 일반화의 오류를 기억할 것이다.
이걸 줄이고 일반화해,
template< class T1, class T2 >
auto& print( T1& os, const T& some )
{
for( auto one : some )
os << one << std::endl;
return os;
}
이렇게 한다면, 당신의 print 함수는 print( int, int ) 같은 기능을 추가 지원할 수 없게 된다.
아이러니하게도 약간은 구체성과 제약을 갖는게
정의간의 간섭을 줄일 수 있다는 것이다.
여기다 연산자 오버로딩( 실은 오버라이딩 ) 을 끼얹으면,
template< class T >
auto& operator+=( std::ostream& os, const T& some )
{
for( auto one : some )
os << one << std::endl;
return os;
}
이런 표현을 쓸 수 있다. 눈치 빠른 사람은 알겠지만 operator+= 까지가 이름이 되었고,
사용할때만 += 이란 이름으로 쓸 수 있는 함수라고 생각하면 된다.
std::cout += some; 처럼 사용하면 된다.
다만, 이경우 std::cout 에 원래 정의되어 있던 << 연산자와 우선순위가 달라지기 때문에,
연속된 출력 연산의 경우
순차가 될 수 있도록 연산자 우선순위를 고려해 괄호를 어딘가 싸줘야 할 것이다.
연산자는 함수보다 더 일반화 된 형태기 때문에,
논리적 충돌 가능성은 더 높다고 하겠다.
알고 쓰면 좋지만, 정말 충돌이 없는 경우 맛깔스럽게 쓰는 정도가 정신건강에 이롭고,
기존에 정의된 연산자 오버로딩이 있다면 가급적 사용하지 않는게 좋다.
이제 한 가지 variation 이 남은것 같다.
지금의 코드는 항목 하나 단위로 개행을 실시하고 있다.
옆으로 나란히 붙여서 찍고 싶으면 어떻게 해야할까? ( 0 1 2 3 4 5... 처럼 )
template< class T >
auto& operator+=( std::ostream& os, const T& some )
{
for( auto one : some )
os << one << " ";
return os;
}
처럼 바꾸면 간단하지만, 다 출력한 후 마지막 스페이스가 거슬릴 경우가 있다.
[ 0 1 2 3 4 5 ] 처럼 브라켓 '[' 와 멤버 사이에 한 칸을 띄우면 문제는 심플해진다.
template< class T >
auto& operator+=( std::ostream& os, const T& some )
{
os << "[ "; // 미리 한칸 띄었으니 제일 마지막 띄움이 등장해도 대칭이다
for( auto one : some )
os << one << " ";
os << "]";
return os;
}
하면 되잖아?
그런데 아무래도 뒤의 공백을 없애고싶다면 ranged_for 를 포기하거나
( 앞 강의의 iterator 참고, 마지막 한 개만 따로 찍기 )
꼼수로 출력한 스페이스를 지우는 방법이 있겠다. ( 변태스럽다. 근데 답은 맞다 )
template< class T >
auto& operator+=( std::ostream& os, const T& some )
{
os << '[';
for( auto one : some )
os << one << " ";
os << "]"; // 백슬래시 b 를 앞에 넣었다. 글에 안나올 뿐.
return os;
}
어느쪽을 선호하든 여러분의 선택.
여기도 일반화와 특수화의 개념이 포함되었다 하겠다.
그런데 기왕 vector 가 나온김에 한 가지는 짚고 가자.
vector< type > 에서, type 에 bool 이 해당하는 경우는, 다른 type 과 달리 특수한 경우다.
보통 bool 은 1바이트 단위 자료형으로 선언이 되어 있지만,
vector< bool > 은 메모리 공간 최적화를 위해 비트 단위로 저장된다.
따라서 개수 단위로 위치가 지정되지 않기 때문에 begin(), end() 는 있지만
data() 인터페이스 자체가 없다.
( 포인터로 쓰지마~ 우리 컨셉이랑 달라~ 란 뜻 )
다음 시간엔 템플릿으로 수열을 제너레이팅해 보겠다.
코세오빵 너무좋아용
백슬래시 비가 안찍혀 있어서 수정완료~
개추가 꽝
ㅇㅂ - dc App
씨벌 이게 머시여
코세 아직도 있네
초개념갤 프갤이 프로레슬링 갤러리가 아니라니 이게 웬일
초개념?
프로그래밍 갤이 초개념이라니 특이하구만
뭔지 몰라 읽다가 잠이 들었다 기호만 보면 잠이오는걸 보면 나는 전생에 노비가 아니었을까
진짜 다시볼때마다 너무멋지다