살군의 보조기억 장치

Another memory device…

Template 메소드의 virtual 사용 문제 (c++)

leave a comment »

한참 작업을 하다보니 상세 구현 부분에서 재미난 문제에 많이 부딪쳤다. 이 가운데 가장 핵심이 되는 문제가 바로 c++ 의 template function (혹은 method)에서 virtual 키워드를 사용할 수 있느냐에 대한 문제이다.

결론부터 먼저 이야기하면… 사용할 수 없다.

이 문제 때문에 어떻게 하면 이것을 피해갈 수 있을까에 대해 몇 일을 고민했다. 그냥 중복되는 코드를 몇줄 더 쓰면 되지만 그러기엔 코드가 지저분해지기 때문에 이를 피해갈 수 있는 방법에 대해 생각을 좀 했었다. 덕분에 잘 안쓰는 기능들에 대해 공부할 기회를 얻기도 했고. 예제코드를 한번 살펴보자.

class CV_EXPORTS Formatter
{
    // ...
    virtual void writeRows(std::ostream& out, const Mat& m) const { /*...*/ }
    virtual void writeCols(std::ostream& out, const void* data,
                       const int ncols, const int type) const { /*...*/ }

    template<typename _Tp> void writeElems(std::ostream& out, const _Tp* data, const int nelems, const int cn) const
    {
        typedef typename DataType::work_type _WTp;
        for(int i = 0; i < nelems; i += cn)
        {
            out << (char)elemOpen;
            for(int j = 0; j < cn; j++)
                out << (_WTp)data[i + j] << (j+1 < cn ? (char)elemsep : ' ');
            out << (char)elemClose << (i+cn < nelems ? (char)colsep : ' ');
        }
    }
}

class MatlabFormatter : public Formatter
{
    void write(std::ostream&; out, const Mat& m, const int*, int) const
    {
        CV_Assert(m.dims <= 2);
        writeRows(out, m);
    }
}

위와 같은 상황일 때, 내가 하고싶은 것은 Formattertemplate void writeElems() 을 상속 받아서 MatlabFormatter 에서 사용하고자 하는 모습으로 오버라이딩overriding하고 싶은 것이다. 문제는 c++ 언어의 제약이다. c++ 표준에서 template function 은 클래스에서 dynamic binding 을 지원하지 않는다. 이유를 살펴보면,

Templates are all about the compiler generating code at compile-time. Virtual functions are all about the run-time system figuring out which function to call at run-time.

Once the run-time system figured out it would need to call a templatized virtual function, compilation is all done and the compiler cannot generate the appropriate instance anymore. Therefor, you cannot have virtual member function templates.

However, there are a few powerful and interesting techniques stemming from combining polymorphism and templates, notably so-called type erasure.

즉, 템플릿은 컴파일-타임에서 생성되는 코드인데, virtual 은 런-타임에서 탐지하는 것이기 때문에다. 위의 예와 같은 경우, 컴파일-타임에서 템플릿을 생성하려면 런-타임에서 알아야 할 것을 미리 알고 있어야 되기 때문에 사용이 불가능한 것이다. 게다가 출처가 정확히 기억이 나지 않는데… 누군가가 이렇게 글을 적어놓은 걸 본 적이 있다.

만일 virtual template function 을 사용하고자 한다면, 구조적으로 설계가 잘못된 것이다

완전 뜨끔하다. 근데 나는 저렇게 하고 싶다. 이걸 어떻게 해야될까? 약간의 트릭과 기본적인 클래스 상속에 대한 개념이 필요하다. 기본적으로 클래스를 상속받는다는 것은 상위 클래스의 코드를 재사용하기 위함이다. 단순히 다형성polymorphism을 위한 것이라면 abstract class 를 사용하는 것이 맞다. Java 는 interface라는 걸출한 키워드가 있지만, c++ 는 abstract class 와 pure virtual function 을 활용해야 한다. 한마디로 얘기하면 구조를 변경해야 된다는 것이다.

자! 정리해보자. 템플릿을 virtual 로 사용할 수 없다면 재사용 가능한 코드를 최대한 부모 클래스로 올리되, 템플릿을 자식 클래스에서 모두 공통으로 사용할 수 있도록 하는 것이다. 즉, 템플릿을 각 데이터의 출력만을 담당하고 나머지 Formatter 에서 필요로 하는 것들을 다 template 함수 밖으로 빼내서 자식 클래스에서는 템플릿 함수 자체를 그대로 사용할 수 있도록 만들어주면 된다.

class CV_EXPORTS Formatter
{
    // 자식 클래스에서 재정의 할 수 있는 virtual 함수들...

    template<typename _Tp> void writeElems(std::ostream& out, const Mat& m, const _Tp* data, const int row, const int col, const int cn) const
    {
        typedef typename DataType::work_type _WTp;
        out << (_WTp)data[row*m.cols*CV_MAT_CN(m.type()) + col*CV_MAT_CN(m.type()) + cn];
    }
}

class MatlabFormatter : public Formatter
{
    void writeRows()
    {
        // 부모클래스의 템플릿 함수 호출
        Formatter::writeElems<const char>();
    }
}

나름 고민을 했었는데… 다 써놓고 보니 너무나 당연한 이야기다. 😉

참고
Advertisements

Written by gomiski

2014/03/01 at 5:18 am

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: