As Simple As Possible?

Tags:

http://www.artima.com/cppsource/simple2.html

C++의 복잡한 면에대해서 나온 기사입니다. 내용이 복잡하니 한글로.. (그래야 구글님께 물어보는 사람들에게 도움이 될 듯 해서입니다.)

(1) Curiously Recurring Template (aka a Self-parameterized base class)

특정 클래스의 인스턴스의 수가 몇인지 셀때는 static을 사용합니다. 여기까지는 기초죠.. 아래의 코드는 상속을 사용해 count 기능을 구현 상속시키도록 하는 C++의 방법입니다.

// A base class that provides counting
template<class T> class Counted {
  static int count;
public:
  Counted() {
    ++count;
  }
  Counted(const Counted<T>&) {
    ++count;
  }
  ~Counted() {
    –count;
  }
  static int getCount() {
    return count;
  }
};

template<class T> int Counted<T>::count = 0;

// Curious class definitions
class CountedClass : public Counted<CountedClass> {};
class CountedClass2 : public Counted<CountedClass2> {};

클래스마다 하나의 static 변수를 갖도록 template을 사용하는 기법입니다. 자기 자신을 파라미터로 넘김에따라 베이스 클래스가 자기 자신마다 한개씩 새로이 생기게 됩니다. 이런건 Java의 generics에서는 불가능하죠. 자바는 C++의 템플릿처럼 코드를 복제하는 방식이 아니니까요.

C#의 경우는 잘 모르겠군요. 손뗀지 너무 오래… 우연히 yield를 사용한 C# 의 iteration 코드를 보았는데, 다시봐도 C#의 그 특징은 참 좋더군요. 자바도 의외로 많이 쓰이고 있지만 – 낮에 Audio 관련 오픈 소스 라이브러리를 찾아보는데 쓸만한 녀석 3개중 2개가 자바 – C# 역시 많이 쓰이고 있습니다. (blackhat.com에 올라온 공개 해킹 툴중 상당수가 C#으로 작성.) trax님 말에 따르면 polyphonic C#이 이젠 표준에 합쳐진다는데, 그렇게 된다면 정~~~~말 대단한 언어가 되는 셈입니다. 뭐 지금도 좋긴하지만.

본론으로 돌아와서..
Python에서는 다음과 같이 한다는군요.

class Shape(object):
    _count = 0 # A shared value for Shape classes with no current objects
  
    @classmethod
    def _incr(cls):
        try:
            cls._count += 1
        except AttributeError:
            cls._count = 1 # adds _count to class object namespace
      
    @classmethod
    def showCount(cls):
        print ‘Class %s has count = %s’ % (cls.__name__, cls._count)
        
    def __init__(self): # A constructor
        self._incr()

class Point(Shape): pass # an empty subclass of Shape

class Line(Shape): pass # ditto

여기서 @classmethod는 클래스 메소드로서 Class Object(java의 java.lang.Class, C++의 std::type_info, C#의 System.Reflection.Type격에 해당함)를 전달받습니다. 결국 Point 나 Line은 각각 자기자신을 기술하는 Class Object를 Shape에서 상속받은 _incr, showCount에게 넘겨주게됩니다. 따라서 각자 자신의 인스턴스 수를 셀 수 있게 됩니다.

인용문에서는 이렇게 얘기하더군요. ‘C++에서는 첫날에 template을 배우지도 않는다.’ ㅋㅋㅋ 맞는 말입니다. 그러나 이 경우엔 솔직히 Python에 너무 미려한 기능이 있어서 C++이 손해본 셈이죠..

(2) Scott Meyers가 C++에 대해서 가장 싫어하는 점 세가지

“I’d like to answer this question with ‘complexity, complexity, complexity!’, but naming the same thing three times is cheating. Still, I think that C++’s greatest weakness is complexity. For almost every rule in C++, there are exceptions, and often there are exceptions to the exceptions. For example, const objects can’t be modified, unless you cast away their constness, in which case they can, unless they were originally defined to be const, in which case the attempted modifications yield undefined behavior. As another example, names in base classes are visible in derived classes, unless the base class is instantiated from a template, in which case they’re not, unless the derived class has employed a using declaration for them, in which case they are.”

복잡한게 싫다는군요. (영어 표현보세요.. 역시 Scott Meyers 답지 않나요? ㅋㅋ 저두 저런 미려한 문장을 쓰고 싶은. 흑흑.) 그러면서 예를 들고 있습니다. 첫번째 예는 const에 대한 것입니다.

#include <iostream>

using namespace std;

void foo(const int *c)
{
    int *d = const_cast<int *>(c);
    *d = 3;
    cout << *d << endl;
}

int main()
{
    const int a = 1; // a is +r
    int b = 2;       // b is +rw

    foo(&a); // raise undefined behavior
    foo(&b); // valid

    return EXIT_SUCCESS; 
}

이 예에서 보다시피 원래 const 인 변수의 주소를 const int *로 잘 받아놓고, 이를 const_cast를 사용해 constness를 벗겨버린뒤 값을 변경하면 안됩니다. 원래 const였으니까요. 그러나 위 코드는 gcc 에선 아무런 에러도 안납니다. (gcc -o test test.cpp -W -Wall 사용시)

두번째로 Meyers가 지적하는 부분은 원래 베이스 클래스의 내용은 서브 클래스에서 볼 수 있어야하지만, 베이스 클래스가 템플릿인경우에는 자식 클래스에서 볼 수 없도록 기본적으로 되어있다는 내용입니다. 이 부분은 전에 쓴글이 있으니, http://mkseo.pe.kr/archives/001200.html 를 참고하세요. Artima글에서는 using을 써야한다고 나오는데 using을 쓰던지 this포인터를 쓰던지 같은 효과입니다.

(3) 템플릿 클래스의 친구들

template class의 friend method/function 또는 friend class입니다. 이 내용을 이해하려면 template의 specialization 에 대해서 알고 있어야합니다.

template<typename T>
class Box {
    T value;
public:
    Box(const T& t) {
        value = t;
    }
    friend ostream& operator<<(ostream&, const Box<T>&);
};

template<typename T> ostream& operator<<(ostream os, const Box<T> b) { return os
<< b.value; }

컴파일 될까요? 답은 안된다입니다. 그 이유는, operator<<이 외부에서는 템플릿처럼 보이게 선언되었으나 Box내에서는 템플릿이 아닌 것처럼 선언되어 혼동을 주기 때문입니다. operator<<는 Box의 개별 인스턴스마다 별도로 생성되야하는지, Box 의 모든 인스턴스에 대해 하나만 생성되야하는지 헷갈리게 되죠. 해결책은 두가지입니다.

첫번째로는 일단 operator<<를 템플릿이라고 해놓고, 우리는 그 템플릿의 specialiation 한개만 친구로 만들어줍니다. 이 경우엔 Box 전체에 대해 하나의 friend만 생겨납니다.

// Forward declarations
template<class T> class Box;
template<class T>
ostream& operator<<(ostream&, const Box<T>&);

template<class T> class Box {
  T value;
public:
    Box(const T& t) {
        value = t;
    }
  friend ostream& operator<< <>(ostream&, const Box<T>&);
};

template<class T> ostream& operator<<(ostream& os, const Box<T>& b) { return  os
<< b.value; }

두번째 해결방법은 operator<< 을 Box의 모든 specialization마다 다 따로따로 만들어서 친구를 각각 맺어줍니다.

template<typename T>
class Box {
    T value;
public:
    Box(const T& t) {
        value = t;
    }
    friend ostream& operator<<(ostream& os, const Box<T>& b) {
      return os << b.value;
    }
};

추가적으로 MSDN에 좋은 글이 있으니 참고하세요.
http://msdn2.microsoft.com/library/f1b2td24(en-us,vs.80).aspx

뭐 결론은 C++ 어렵다입니다. 스트라우스트롭도 C++의 차기 버전에 대한 인터뷰에서 ‘C++ 언어는 전문가를 중심으로 발전해왔다’고 인정하더군요.

UPDATE:
(2006-09-24) http://www.parashift.com/c++-faq-lite/templates.html#faq-35.16 에도 설명이 있습니다.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *