레이블이 씨뿔뿔인 게시물을 표시합니다. 모든 게시물 표시
레이블이 씨뿔뿔인 게시물을 표시합니다. 모든 게시물 표시

2016년 3월 22일 화요일

[c++] 순수가상클래스와 인터페이스의 차이점

오랜만에 C++ 코딩을 하느라 정신이 없습니다. 그런 와중에도 한가지 적을만한 내용이 있어서 적어봅니다.

C++ 에는 아시다 시피 interface 라는 것이 컴파일러 차원에서 재공되지 않습니다. Java 에서는 interface 라는 키워드도 있고
interface 구현 할때는 내부 구현을 못하도록 강제로 막아 주기도 하지요. 디자인 적인 차원에서 매우 훌륭한 특징이라고 생각합니다.

대신에
C++ 에서는 Abstract class 라는 것을 지원하는데 구현 함수중의 적어도 한개를 virtual 로 선언하고 구현을 Null 값으로 할당해서 구현을 비워두면 자동으로 추상 클래스가 됩니다. Java 의 interface 와의 차이점은, Java 에서는 내부 구현을 전혀 못하도록 강제하는 반면에  C++ 에서는 일부 구현을 허용한다는 점이 다릅니다.

그러면 C++ 의 추상 클래스를 사용해서 Java 의 interface 와 완전히 똑같은 것을 사용하려면 어떻게 해야하는가? 하는 것이 사실 이 글의 요점입니다.
컴파일러의 도움없이 수작업으로 모든 함수들을 공란으로 비워두면 interface 와 비슷해지지만, 완전히 같아지지는 않습니다. 문제는 C++ 에서는 interface 같은 형태로 사용하려면 다중 상속의 개념이 적용되는데 C++ 에서 상속을 할때에는 반듯이 소멸자가 virtual 로 선언되어야 합니다. 그런데 소멸자는 선언이 되면 반듯이 구현도 같이 해줘야 하는 문제가 덩달아 생겨납니다.

그래서 아무리 비슷하게 하려고 해도 아래와 같이 소멸자는 구현이 되어야 한다는 것입니다.

class EventListener {
public:
    virtual ~EventListener() {} // 이 부분이 구현이 된 것입니다.

    virtual void handleEvent(EventPtr evt) = 0; // 함수를 virtual 로 선언하고 구현을 비워둡니다.
};
그래서 결론은 비슷하게 사용할수 있지만 interface 인 녀석이 구현을 담고 있다는 것이 여전히 꺼림직 합니다.

소멸자는 순수 가상함수로 선언할수 없습니다. 

추가로(참고 : http://pacs.tistory.com/35)
class EventListener {

public:
   virtual ~EventListener() = 0; // 순수 가상 소멸자의 선언. 이렇게 수행할 수 없는게 아니다.

};
 EventListener::~ EventListener() {} //순수 가상 소멸자의 선언. 다만 순수 가상소멸자는 반드시 정의를 해 두어야 하기에 위의 예제와같이 작성한다.

2016년 3월 20일 일요일

[Effective C++] 항목 14 : 자원관리 클래스의 복사 동작에 대해 진지하게 고찰하자.

- 모든 자원이 힙에서 생기지는 않기 때문에, 우리는 필요에 따라, 자원 관리 클래스를 직접 만들어야 할 경우도 있습니다.

- RAII 기법에 따라, 뮤텍스 잠금을 관리하는 클래스를 만든다면, 아래 예제 처럼 작성 할 수 있습니다.

RAII 패턴은 C++ 같이 개발자가 직접 resource 관리를 해주어야 하는 언어에서 leak 을 방지하기 위한 중요한 기법으로 해당 리소스의 사용 scope이 끝날 경우에 자동으로 해제를 해주며 exception이 발생하거나 하는 경우에도 획득한 자원이 해제됨을 보장하여
robust code 코드를 작성할 수 있다.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Lock
{
public:
    explicit Lock(Mutex *pm) : mutexPtr(pm)
    {
        lock(mutexPtr);
    }
    ~Lock() { unlock(mutexPtr); }
private:
    Mutex *mutexPtr;
}
void main()
{
    Mutex m;
    {
        Lock m1(&m);
    }
}

 - 생성시, 잠금을 걸고, 블럭을 나가면서 잠금을 해제할 것 입니다.
  만약, 같은 객체로 Lock을 한번 더 하게 된다면, 어떻게 되어야 할까요?
?
1
2
Lock m1(&m);
Lock m2(m1);


아래의 규칙을 참고하여, 해당 하는 내용을 작성해 봅니다.

1. 복사를 금지 합니다.
   복사하면 안되는 RAII 클래스에 대해서는 반드시 복사가 되지 않도록 막아야 합니다.
?
1
2
class Lock : private Uncopyable
{
  
2. 관리하고 있는 자원에 대해 참조 카운팅을 수행합니다.
   해당 자원을 참조하는 객체의 개수에 대한 카운트를 증가시키는 식으로 RAII 객체의 복사동작을 만들어야 합니다.
   tr1::shared_ptr를 사용 할 수 있는데, 기본 동작이 참조 카운트가 0이 되면 대상을 삭제해 버리기 때문에,
   삭제자를 지정하여 사용 합니다. 삭제자는 참조 카운트가 0이 되었을 때 호출되는 함수 혹은 함수 객체를 말합니다.
?
1
2
3
4
5
6
7
8
9
10
11
class Lock
{
public:
    explicit Lock(Mutex *pm) : mutexPtr(pm, unlock)
    {
        lock( mutexPtr.get() );
    }
private:
    std::tr1::shared_ptr<mutex> mutexPtr;
}
</mutex>
- 위 코드를 보면, 소멸자 선언이 사라졌습니다.
  클래스의 소멸자는 비정적 데이터 멤버의 소멸자를 자동으로 호출하게 되어 있습니다.
  이 '비정적 데이터 멤버'에 해당하는 것이 mutexPtr입니다.
  그런데, mutexPtr의 소멸자는 뮤텍스의 참조 카운트가 0이 될 때, 삭제자를 자동으로 호출합니다.

  위 코드에서는 컴파일러가 생성한 소멸자를 통해, 뮤텍스의 참조 카운트가 0이 되면, unlock을 호출합니다.

3. 관리하고 있는 자원을 진짜로 복사합니다.
 - 때에 따라서는 자원을 원하는 대로 복사 할 수도 있습니다.
   자원 관리 객체를 복사하면 그 객체가 둘러싸고 있는 자원까지 복사되어야 합니다. [깊은 복사(deep copy)]

4. 관리하고 있는 자원의 소유권을 옮깁니다.
 - 특정한 자원에 대해 그 자원을 실제로 참조하는 RAII 객체는 딱 하나만 존재하도록 만들고 싶을때,
   auto_ptr과 같이 소유권을 이동하는 동작을 생각 할 수 있습니다.

* RAII 객체이 복사는 그 객체가 관리하는 자원의 복사 문제를 안고 가기 때문에,
  그 자원을 어떻게 복사하느냐에 따라 RAII 객체의 복사 동작이 결정 됩니다.  
* RAII 클래스에 구현하는 일반적인 복사 동작은 복사를 금지하거나 참조 카운팅을 해주는 선으로 마무리하는 것입니다.
  하지만 이 외의 방법들도 가능하니 참고해 둡시다.

[Effective C++] 항목 30 : 인라인 함수는 미주알고주알 따져서 이해해 두자.

인라인 함수를 사용하면 컴파일러가 함수 본문에 대해 문맥별 최적화를 걸기가 용이해집니다. 인라인 함수의 아이디어는  함수 호출문을 그 함수의 본문으로 바꿔치기하자는 것  남발했다가는 코드의 크기가 커질 게 뻔하다. 인라인 함수로 부풀려진 ...