2016년 9월 27일 화요일

Static Polymorphism(정적 다형성)

정적 다형성, 컴파일 타임 다형성, 템플릿. 일맥상통하는 의미를 가지고 있는 용어들이다. 반대되는 개념은 동적 다형성, 런타임 다형성, 동적 바인딩, virtual function 등이 되겠다.
다음의 코드를 통해 정적 다형성의 의미를 확인해 보자.


 struct TreeNode { TreeNode *left, *right; }; 

 class GenericParser {  
 public :  
   void parsePreorder(TreeNode* node)  
   {  
     if(node)  
     {  
       processNode(node);  
       parsePreorder(node->left);  
       parsePreorder(node->right);  
     }    
   }    
 private:  
   virtual void processNode(TreeNode* node) { }  // virtual function 입니다.
 };   
     
 class EmployeeChartParser : public GenericParser {  
 private :  
   void processNode(TreeNode* node)  
   {  
     cout << "Customized processNode() for EmployeeChart.\n";  
   }  
 };   
위 코드는 전형적인 가상 함수를 사용한 동적 다형성의 예이다.
익히 알려진 바와 같이 가상 함수 사용을 위해서는 vTable이 추가됨과 동시에 함수의 동적 바인딩에 따른 비용이 추가된다.
이 코드에 정적 다형성 기술을 사용하여 동적 다형성에 추가되는 비용을 제거하도록 해보자.
 
 struct TreeNode { TreeNode *left, *right; };  
  
 template<typename T>  
 class GenericParser {  // class template 입니다. CRTP 기술을 사용합니다.
 public :   
   void parsePreorder(TreeNode* node)   
   {    
     if(node)  
     {    
       processNode(node);  
       parsePreorder(node->left);  
       parsePreorder(node->right);  
     }  
   }  
 private :  
   void processNode(TreeNode* node)  // 가상 함수가 아닙니다.
   {  
     static_cast<T*>(this)->processNode(node);  // 상속 관계이기 때문에 casting을 통해 안전하게 
                                                // 하위 클래스의 멤버 함수를 호출할 수 있습니다.
   }  
 };   
     
 class EmployeeChartParser : public GenericParser<EmployeeChartParser> {  
 public :  
   void processNode(TreeNode* node)  
   {  
     cout << "Customized processNode() for EmployeeChart.\n";  
   }  
 };   
CRTP(Curiously Recurring Template Pattern)는 템플릿 매개변수로 하위 클래스 타입을 받아 만들어진 부모 클래스로부터 다시 하위 클래스가 파생된 모습을 일컫는 용어다. 스캇 마이어스는 그의 저서 "Effective C++"에서 CRTP 대신에 "나만의 것(Do It For Me)" 이라는 이름을 제안했다고 하는데, CRTP의 성격을 잘 표현한 적절한 이름인 것 같다.

위 코드는 template과 CRTP 기술을 사용하여 vTable 생성과 함수의 동적 바인딩 비용을 제거하였다.
헌데 옥의티가 보인다. EmployeeChartParser 클래스의 processNode 함수의 접근제한자가 private에서 public으로 바뀐 것이 눈에 뜨인다. 즉, 객체의 캡슐화가 약해졌다. 이는 더 이상 processNode 함수가 가상 함수가 아니기 때문에 하위 클래스 함수의 접근제한자 변경이 동반된 것.

정적 다형성의 유익과 함께 클래스 캡슐화를 유지하기 위해 부모 클래스를 friend 선언하도록 하면 문제가 해결될 것 같다. 이제 마지막 코드다.

 struct TreeNode { TreeNode *left, *right; };  
   
 template<typename T>  
 class GenericParser {  
 public :   
   void parsePreorder(TreeNode* node)   
   {    
     if(node)  
     {    
       processNode(node);  
       parsePreorder(node->left);  
       parsePreorder(node->right);  
     }  
   }  
 private :  
   void processNode(TreeNode* node)  
   {  
     static_cast<T*>(this)->processNode(node);  
   }  
 };   
     
 class EmployeeChartParser : public GenericParser<EmployeeChartParser> {  
 private :  
   void processNode(TreeNode* node)   // 이제 private 함수로 선언할 수 있습니다.
   {  
     cout << "Customized processNode() for EmployeeChart.\n";  
   }  
   
   friend class GenericParser<EmployeeChartParser>;  // 부모 클래스를 friend 선언합니다.
 };  

댓글 없음:

댓글 쓰기