Sealed Macro

2009/07/05 06:50
모던 C++ 디자인이라는 책은 봐도봐도 새로운 내용이 나온다.
그책에서 건진내용중에 하나인데, 클래스가 상속을 받을때 virtual table이 그냥 생기는것이 아니라 virtual 함수가 있을때만 생긴다는것 - '단위전략'은 CRTP를 쓰기위해 다중상속을 활용하는데, 부모클래스에 virtual 함수가 있으면 부모마다 vtable이 생겨 오버헤드라는 이야기 - 이다.

나는 소멸자를 virtual로 만들지 않고 상속받을때의 무시무시함을 겪은적이 있다. 그래서 소멸자는 항상 virtual - 이것이 상속받을이유가 없는 클래스라도 - 로 하곤 했었는데, 그럴때마다 vtable을 만들거라 생각하니 찝찝했다. 그렇다고 상속을 허용하면서 소멸자를 virtual로 안하는것은 정말 귀신피하려다 호랑이 만나는꼴 당할수 있는일이라, 상속을 못받게 하는방법을 찾아보았다.

몇가지 좋은정보들을 찾았는데, 전부 비얀할아버지가 작성한것을 기반으로 하고 있고 응용버전으로 birdkr님의 템플렛버전이 있었다. 그런데 gcc에서는 friend T는 안된다. - 사실, friend T는 표준 C++  문법이 아니다.

좀더 찾아보니 Boost Mailing List 에도 비슷한 주제가 올라왔었고 friend T 문제를 어느정도 해결한 방법을사용한 최종버전도 있더라 - 저것이 표준 Boost Library로 승인되었는지는 잘 모르겠다.

하늘은 뒤지는자의 편이라 했던가. (음?)
써먹기에는 영 마음에 들지 않아서 더 뒤져보니 그럭저럭 마음에 드는 버전을 찾을수 있었다. 옮겨보면,

(Language : cpp)
#define SEALED(className) \
    className ## Sealer \
        { \
            private: className ## Sealer(){}; \
            friend class className; \
        }; \
        class className : virtual private className ## Sealer \

class SEALED(MyClass) {};

무엇보다 비록 매크로지만 - 아니, 매크로라서 - 사용에 있어서 훨씬 직관적인 장점이 있다.

프로그래밍 C++, 프로그래밍

어차피 io는 느린걸

2009/02/08 17:02
C의 printf 계열보다 C++ 의 cout 등의 io 계열이 많이 느리다는건 익히 알려진 문제이다. 그런데 printf계열은 가변인자를 사용하여 printf와 비슷한 함수를 직접만드려면 어쩔수없이 va_list 계열의 매크로를 건드려야한다.

게다가 printf 계열은 버퍼오퍼플로를 막기위해 많은 변종들이 생겨나서 어떤게 표준인지 했갈리기도 한다. c99 표준이라고 알고 있는 snprintf는 Visual Studio 2008에서 조차 지원하지 않는다.

이런거 저런거 다 따져보면 차라리 C++ 계열의 io를 사용하는게 낫겠다는 생각이 들었다.
가변인자를 오버로딩으로 처리하면 formatting이 없는 경우에는 오히려 printf보다 빠르겠다는 생각도 들었다.

그래서 logger를 이런식으로 구현해봤다.
(Language : cpp)
static void log( const char* _pSzContents )
{
    onPreSetMessage_();
    pLogger_->strCurrentMessage_ = _pSzContents;
    onPostSetMessage_();
}

template< typename T0, typename T1 >
static void log( const char* _pSzContents, T0 _t0, T1 _t1 )
{
    onPreSetMessage_();
    EStringUtils::formatStr(pLogger_->strCurrentMessage_, _pSzContents, _t0, _t1);
    onPostSetMessage_();
}

template< typename T0, typename T1, typename T2 >
static void log( const char* _pSzContents, T0 _t0, T1 _t1, T2 _t2 )
{
    onPreSetMessage_();
    EStringUtils::formatStr(pLogger_->strCurrentMessage_, _pSzContents, _t0, _t1, _t2);
    onPostSetMessage_();
}
...


물론 formatStr로 비슷하게 구현되어 있다.
(Language : cpp)
template< typename T0 >
static void formatStr(std::string& _rString, const char* _pSzContents, T0 _t0 )
{
    formatStr(_rString, _pSzContents, _t0, FORMAT_INDEX_ERROR);
}

template< typename T0, typename T1 >
static void formatStr(std::string& _rString, const char* _pSzContents, T0 _t0, T1 _t1 )
{
    formatStr(_rString, _pSzContents, _t0, _t1, FORMAT_INDEX_ERROR);
}

...

static void formatStr(std::string& _rString, const char* _pSzContents, T0 _t0, T1 _t1, T2 _t2, T3 _t3, T4 _t4, T5 _t5, T6 _t6, T7 _t7 )
{
    assert(_rString.empty());
    std::vector< std::string > vBuffers;
    splitFormat_(_pSzContents, vBuffers);

    std::ostringstream oss;
    ...
   
}

뭐, 어차피 io는 느린걸...

관련 자료들
 - printf vs. cout
 - ELogger.h
 - EStringUtils.h

프로그래밍 C++, 프로그래밍

T-Rex : 작은 C++ 정규표현식 라이브러리

2009/02/07 21:25
C++에서 C# 형태의 스트링 포메팅을 지원하는 로거를 만들다보니 정규표현식을 지원하는 라이브러리가 필요하게 되었다. 유명한 boost 에 정규표현식 지원 라이브러리가 있었지만 여러프로젝트의 기반이 되는 프로젝트에 포함시킬 생각이어서 코드상태로 프로젝트에 포함될정도 작아야했다.

sf에서 뒤지다가 딱 필요한 정도의 기능만 구현되어 있는 t-rex라는 라이브러리를 발견했다. zlib 라이센스에 파일도 헤더파일, cpp파일 2개만 있으면 쓸수 있을정도로 작았고 사용방법도 아주 간단했다.

세미콜론에 비교적 안정적인 path splitter(앞뒤로 세미콜론이 있을수 있고, 세미콜론사이에 여러개의 세미콜론이 들어가도 분리가능하고 맨 끝은 세미콜론이 없어도 나누어줄수 있는) 를 요 라이브러리로 구현하면 다음과 같다.

(Language : cpp)
std::vector< std::string > vecSplittedPath;

const char* pSzContents = ";;/usr;;/opt/lib;/sbin;;/etc";
const char* pSzError = 0;
const char* pSzPattern = "[^;]+";
TRex* pRex = trex_compile(pSzPattern, &pSzError);
if(pRex)
{
    const char *pSzBegin,*pSzEnd;
    while(trex_search(pRex, pSzContents, &pSzBegin, &pSzEnd) )
    {
        TRexMatch match;
        trex_getsubexp(pRex,0,&match);
        vecSplittedPath.push_back( std::string(match.begin, match.len) );

        pSzContents = pSzEnd;
    }
}
trex_free(pRex);

프로그래밍 C++, 프로그래밍

CRTP와 멤버함수포인터

2008/05/20 21:13
회사에서 네트워크 관련 라이브러리에서 CRTP를 활용할 수 있는 예가 있어서 만들어봤던 코드인데, 회사내의 정책이 바뀌어서 쓸모가 없어진 비운의 코드이다. 자랑할겸(-_-) 소스정리 해보았다. CRTP에 대해서는 예전글 참조

네트워크나 스크립트 엔진등에서 함수를 문자열을 통해 호출하는 경우가 많다. 예를들면

(Language : cpp)
CHelloPok aTestPok("Test pok on hello world 'key'...");
aTestPok.callFunc("key");
aTestPok.callFunc("id");

이런식으로 callFunc라는 메쏘드를 통해 "key"와 연결된 함수를 호출한다. 이렇게 하기 위해 일반적으로 두가지 방법을 사용한다. "key"에 연결된것이 static member 함수여서 map 자료형에 함수포인터를 담아놓거나 혹은 callFunc자체를 virtual로 두고 자식클래스 단에서 그것을 구현하는 경우다. 전자의 경우는 널리 사용되는 방법인데 멤버에 대한 접근성이 많이 부족하고 후자의 경우는 자식클래스단에서의 구현 알고리즘이 똑같은데 - 문자열에 대응하는 멤버함수를 연결 - 멤버함수마다 자료형이 달라서 어쩔수 없이 자식클래스 마다 따로 구현해야되는 문제때문에 많이 쓰이지는 않는다.

후자의 문제를 해결하기 위해 접근했던 방법이, 부모가 알고리즘을 가지고 자식의 자료형을 부모가 알고 있게 하면 어떻게 할수 있지 않을까 였고, 부모가 자식의 자료형을 알고 있게 하기 위해 CRTP를 사용하였다.

구현 소스를 보면 일단 호출 인터페이스가 있고

(Language : cpp)
class CrtpInterface
{
public:
    virtual ~CrtpInterface(){}
    virtual void callFunc(const char* _pSzFuncId) = 0;
};

그것을 상속받은 CRTP의 중간객체가 존재한다.

(Language : cpp)
template <typename T>
class CrtpImpl : public CrtpInterface
{
public:
    virtual void callFunc(const char* _pSzFuncKey)
    {
        FuncMap::iterator itr = funcMap_.find(_pSzFuncKey);
        if( itr == funcMap_.end() )
        {
            printf("Not Found '%s!'\n", _pSzFuncKey);
            return;
        }

        // call func
        (static_cast<T*>(this)->*(itr->second))();
    }

protected:
    typedef void (T::*Func)(void);
    void registerFunc(const char* _pSzFuncKey, Func _pFunc)
    {
        funcMap_[ _pSzFuncKey ] = _pFunc;
    }

private:
    typedef std::map< std::string, Func > FuncMap;
    FuncMap funcMap_;
};

몇가지 신기한 자료형이 나오는데, 메범함수 포인터 자료형은( (static_cast<T*>(this)->*(itr->second))(); ) C++의 재미있는 자료형중 하나이다. 뭐랄까.. 좀 심오한 자료형인데, 알렉산더레스쿠(-_- 맞나..)의 Modern C++ Design에 그에 대한 자세한 설명이 있다.(일반화된 Command Functor 설명부분에 있었던듯 하다.)

그리고 본격적으로 상속받아 기능을 구현하는 클래스들이 있고 그 클래스들을 활용하는 메인루프가 있을것이다.

(Language : cpp)
class CHelloWorld : public CrtpImpl< CHelloWorld >
{
public:
    CHelloWorld()
    {
        // key 라는 문자열에 CHelloWorld::helloWorld 멤버함수 연결
        registerFunc("key", &CHelloWorld::helloWorld );
    }

    void helloWorld()
    {
        printf("Hello World!\n");
    }
};

class CHelloPok : public CrtpImpl< CHelloPok >
{
public:
    CHelloPok(const char* _pSzContents) : strToSay_( _pSzContents )
    {
        // key 라는 문자열에 CHelloPok::helloPok 멤버함수 연결
        registerFunc("key", &CHelloPok::helloPok );

        // id 라는 문자열에 CHelloPok::whoAmI 멤버함수 연결
        registerFunc("id", &CHelloPok::whoAmI );
    }

    void helloPok()
    {
        printf("%s\n", strToSay_.c_str() );
    }

    void whoAmI()
    {
        printf("Hey, it's me. pok.\n\n");
    }

private:
    std::string strToSay_;
};

(Language : cpp)

int _tmain(int argc, _TCHAR* argv[])
{
    // 함수호출 테스트
    CHelloPok aTestPok("Test pok on hello world 'key'...");
    aTestPok.callFunc("key");
    aTestPok.callFunc("id");

    // 다형성 테스트
    CHelloWorld aHelloWorld;
    CHelloPok aHelloPok("Hello pok!");

    std::vector< CrtpInterface* > vecFunc;
    vecFunc.push_back(&aHelloWorld);
    vecFunc.push_back(&aHelloPok);

    for(std::vector< CrtpInterface* >::iterator itr = vecFunc.begin();
        itr != vecFunc.end(); ++itr )
    {
        (*itr)->callFunc("key");
    }
    return 0;
}

프로그래밍 C++, 프로그래밍

비트연산자를 통한 프로퍼티 구현

2008/02/29 22:53
자주 확인하지 않는 프로퍼티들이 bool 값으로 들어있는것은 아깝다. 이때는 비트연산자를 이용해서 프로퍼티들 관리하는것이 좋다.

(Language : cpp)
enum PROP
{
    PROP_1 = 1 << 1,
    PROP_2 = 1 << 2
};

int g_prop = 0;

void AddProperty(PROP _eProp)
{
    g_prop |= _eProp;
}

void RemoveProperty(PROP _eProp)
{
    g_prop &= ~_eProp;
}

bool QueryProperty(PROP _eProp)
{
    if( (g_prop & _eProp) == _eProp )
        return true;
    return false;
    // 혹은
    // return (g_prop & _eProp) ? true : false;
}

프로그래밍 C/C++, 프로그래밍

어느 회사의 프로그래밍 입사문제

2007/11/18 15:01
친구녀석이 자기가 본 모 회사의 입사 프로그래밍 문제를 말해줬다.

어떤 배열이 있는데 그 배열의 원소는 음수, 양수의 값을 가지는 정수이다. 이 배열중 원소의 합이 가장큰 구간을 O(n)의 복잡도로 구하는 알고리듬을 프로그래밍 하라.

원래 이런 종류의 두뇌개발용 프로그래밍은 별로 좋아하지는 않지만, 숙제와 시험에 생성된(...) 뻘짓거리 찾기 탐지망에 딱걸려서 이리저리 생각을 해보았다.

생각해낸 방법은 딱 한번 순회는 아니지만, O(n) = O(2n) 이라고 우기고 들어가서, 다음과 같다.
1. 배열을 순회하면서 구간을 음수구간의 덩어리와 양수구간의 덩어리로 나눈다.
2. 구간 덩어리중 제일 큰 양수구간 덩어리를 선택한다.
3. 제일큰 양수구간 덩어리를 중심으로 작은 구간으로 순회하며 값을 더한다.
    3.1 만일 더한값이 제일큰 양수구간의 합보다 크면 제일큰 양수구간을 변경한다.
4. 제일큰 양수구간 덩어리를 중심으로 큰 구간으로 순회하며 값을 더한다.
   4.1 만일 더한값이 제일큰 양수구간의 합보다 크면 제일큰 양수구간을 변경한다.

아래는 잘 돌아가는가 확인하기 위해 루비로 짠 코드.

(Language : perl)
class RangeChunk
  attr_accessor :r_start, :r_end, :r_sum
  def initialize( nStart )
    @r_start = nStart
    @r_end = 0
    @r_sum = 0
  end
end

class StrangeRange
  def initialize( _rangeSize = 0 )
    @totalRange = Array.new( _rangeSize )
    nCount = 0
    @totalRange.each do | element |
      # -500 ~ 500
      element = 500 - rand(1000)
      @totalRange[nCount] = element
      print nCount.to_s +  ". " + element.to_s +  "\n"
      nCount += 1
    end
  end
 
  def selectBigRange
    nCurrnetIndex = 0
    nCurrnetBigRangeChunk = 0
    nCurrentBigSumOfChunk = 0
    bPlusRange = true
   
    aChunkArray = Array.new
    aRangeChunk = RangeChunk.new(0)
   
    @totalRange.each do | element |
      if bPlusRange
        if element >= 0
          aRangeChunk.r_end = nCurrnetIndex
          aRangeChunk.r_sum += element
        else
          bPlusRange = false
          # 처음을 양수구간이라고 가정했는데, 알고 보니 음수구간이었다면 그냥 음수구간으로 변경시켜주기
          if aRangeChunk.r_end == nCurrnetIndex
            aRangeChunk.r_sum = element
            nCurrentBigSumOfChunk = element
          else
            aRangeChunk.r_end = nCurrnetIndex - 1
            aChunkArray.push( aRangeChunk )
            if aRangeChunk.r_sum > nCurrentBigSumOfChunk
              nCurrentBigSumOfChunk = aRangeChunk.r_sum
              nCurrnetBigRangeChunk = aChunkArray.length - 1
            end
            aRangeChunk = RangeChunk.new( nCurrnetIndex )
            aRangeChunk.r_sum += element
          end
        end
      else
        if element >= 0
          bPlusRange = true
          aRangeChunk.r_end = nCurrnetIndex - 1
          aChunkArray.push( aRangeChunk )
          aRangeChunk = RangeChunk.new( nCurrnetIndex )
          aRangeChunk.r_sum += element
        else
          aRangeChunk.r_end = nCurrnetIndex
          aRangeChunk.r_sum += element
        end
      end
      nCurrnetIndex += 1
    end
    #마지막 구간
    aRangeChunk.r_end = nCurrnetIndex - 1
    aChunkArray.push( aRangeChunk )
   
    # 음수/양수구간으로 나눈게 맞는지 확인하기.
    aChunkArray.each do | chunk_element |
      print chunk_element.r_sum.to_s + "\n"
    end
   
    aBeforChunk = nil
    aAfterChunk = nil
   
    nNewBigSumOfChunk = nCurrentBigSumOfChunk
    (nCurrnetBigRangeChunk-1).step(0, -1) do | i |
      nNewBigSumOfChunk += aChunkArray[i].r_sum
      if nNewBigSumOfChunk > nCurrentBigSumOfChunk
        nCurrentBigSumOfChunk = nNewBigSumOfChunk
        aBeforChunk = aChunkArray[i]
      end
    end
    nNewBigSumOfChunk = nCurrentBigSumOfChunk
    (nCurrnetBigRangeChunk+1).step(aChunkArray.length-1, 1) do | i |
      nNewBigSumOfChunk += aChunkArray[i].r_sum
        if nNewBigSumOfChunk > nCurrentBigSumOfChunk
          nCurrentBigSumOfChunk = nNewBigSumOfChunk
          aAfterChunk = aChunkArray[i]
        end
    end
    nResultStartRange = aBeforChunk == nil ? aChunkArray[nCurrnetBigRangeChunk].r_start : aBeforChunk.r_start
    nResultEndtRange = aAfterChunk == nil ? aChunkArray[nCurrnetBigRangeChunk].r_end : aAfterChunk.r_end
    print "Big Range Start : " + nResultStartRange.to_s + ", End : " + nResultEndtRange.to_s + ". and Sum : " + nCurrentBigSumOfChunk.to_s + "\n"
  end
 
end

if __FILE__ == $0
  nRangeCount = ARGV.empty?  ? 10 : ARGV[0].to_i
  aSample = StrangeRange.new( nRangeCount )
  aSample.selectBigRange
end

나중에 그 친구한테 들은이야기인데, 알고보니 ACM-ICPC 에서의 고전적인 문제라고 한다. ACM-ICPC 같은경우는 전혀 관심이 없었는데, 얼추 재미있는걸 하는구나... 라는 생각이 들었다.

ps. 언제 봐도.. 내 루비코드는 C++ 코드 같다. -_-

프로그래밍 시간복잡도, 알고리즘, 프로그래밍

operator=

2007/08/24 18:12
개인적으로 연산자 오버로딩을 무지 싫어하나 이것들이 잘 구현된 라이브러리들은... 역시 편하다-_-
end의 행렬클래스도 연산자 오버로딩을 하려고, 다른 행렬클래스들을 살펴보았는데, 허걱.=이 붙은 계열의 오버로딩은 모두 참조를 리턴하는게 아니던가! 궁금하던 차에 사수격인 회사동료분께 물어봤더니, 아주 명쾌한 해설을 해주셨다.

operator=를 오버로딩해서 호출이 되면, lhs = rhs 꼴은 lhs.operator=(rhs) 형태로 된다. 만일 a=b=c를 수행한다고 하면, a.operator=( (b.opeator=(c) ) 가 된다. 그런데, a=b=c 는 Primative Type을 기준으로 a는 b와 같은 값이 되어야 하고 그렇게 되기 위해서 b.operator=(c)는 b를 참조형으로 던져야 한다. 또한 a = a  (a=b)=c일 경우도 있을수 있으므로(멍청하게 저렇게 쓰지는 않겠지만, 참조형으로 엮여서 저렇게 된다는지) const를 붙이지 않는 참조형으로 반환해야 한다.

라이브러리를 만든다는것은 정말 쓸모 없는 재발명일수도 있지만, 배우는 입장에서는 무척이나 좋은 경험같다.

프로그래밍 C++, 프로그래밍

묘하게 되풀이되는 템플릿 패턴(CRTP : Curiously Recurring Template Pattern)

2007/08/19 23:30

(Language : cpp)
// 클래스 X는 X 자신을 템플릿 인수로 받는 템플릿 특수화를 기반클래스로 한다
// - from C++ 템플릿 메타프로그래밍, 정보문화사
class X : public base<X>
{
};

코드 그대로..
또한, C++ 템플릿 메타프로그래밍책에 그 활용도 잘 나와 있다.

가장 많이 쓰이는것중 하나가 GPG에 나왔던 상속으로 싱글톤 기능을 수행할수 있도록 하는 코드.
아래는 오우거에서 발췌했다.

(Language : cpp)
namespace Ogre {
// End SJS additions
    /** Template class for creating single-instance global classes.
    */

    template <typename T> class Singleton
    {
    protected:

        static T* ms_Singleton;

    public:
        Singleton( void )
        {
            assert( !ms_Singleton );
#if defined( _MSC_VER ) && _MSC_VER < 1200  
            int offset = (int)(T*)1 - (int)(Singleton <T>*)(T*)1;
            ms_Singleton = (T*)((int)this + offset);
#else
        ms_Singleton = static_cast< T* >( this );
#endif
        }
        ~Singleton( void )
            {  assert( ms_Singleton );  ms_Singleton = 0}
        static T& getSingleton( void )
        {   assert( ms_Singleton )return ( *ms_Singleton ); }
        static T* getSingletonPtr( void )
        { return ms_Singleton; }
    };
}

사실, 그런갑다 하고 썼는데, 회사동료분이 활용하는것을 보고 충격먹고, 활용해먹어야지 생각하고 있다가, 다양한 활용성을 보고나서.. 정말 난 아무것도 아니구나라고 침울해져버렸던 테크닉이다. 앞으로 자주 활용해야지~

CRTP와 멤버함수포인터
MKSeo 님의 CRTP 글

프로그래밍 C++, 프로그래밍

익명 공용체와 익명 구조체 ( anonymous union / anonymous structure )

2007/08/19 22:57
익명의 공용체에 익명 구조체를 쓰는것은 대부분의 C++ 컴파일러가 지원하는 확장이지만 표준은 아니다.
꼴에 표준을 좋아해서 DX에서 쓰이는 아래의 행렬은 새로 작성하고 있는 end 의 행렬객체로는 부적격.

(Language : cpp)
typedef struct _D3DMATRIX {
    union {
        struct {
            float        _11, _12, _13, _14;
            float        _21, _22, _23, _24;
            float        _31, _32, _33, _34;
            float        _41, _42, _43, _44;

        };
        float m[4][4];
    };
} D3DMATRIX;


현재는 이런식으로 행렬 클래스를 설계하고 있다.

(Language : cpp)
namespace end
{
    class Matrix4
    {
    public:

        enum ENTRIES
        {
            _11=0,  _12,  _13,  _14,
            _21,    _22,    _23,    _24,
            _31,    _32,    _33,    _34,
            _41,    _42,    _43,    _44
        };

        //////////////////////////////////////////////////////////////////////
        // 단위행렬 / 영행렬 ---
        //
        void makeIdentity();
        void makeZero();


        //////////////////////////////////////////////////////////////////////
        // 주어진 행렬로부터 행렬을 만드는 경우 ---
        //
        void makeFromInverse( const Matrix4& _rMatToGetInvers );
        void makeFromMultiply(
                const
Matrix4* _pMat1,
                const Matrix4* _pMat2,
                const Matrix4* _pMat3NullIfNotUse = 0,
                const Matrix4* _pMat4NullIfNotUse = 0,
                const Matrix4* _pMat5NullIfNotUse = 0 );


        //////////////////////////////////////////////////////////////////////
        // 변환의 용도로 쓰일 행렬을 만드는 경우 ---
        //
        void makeTransform( const Matrix4& _rMat );

        void makeTransformPerspectiveProjectionLH(
                float _fFieldOfViewRadians = END_PI/4.f,
                float _fAspectRatio = 4.f/3.f,
                float _fZNear = 1.0f, float _fZFar = 1000.0f );
        void makeTransformPerspectiveProjectionRH(
                float _fFieldOfViewRadians = END_PI/4.f,
                float _fAspectRatio = 4.f/3.f,
                float _fZNear = 1.0f, float _fZFar = 1000.0f );

        void makeTransformOrthogonalProjectionLH(
                float _fWidth = 800.0f, float _fHeight = 600.0f,
                float _fZNear = 0.0f, float _fZFar = 1.0f );
        void makeTransformOrthogonalProjectionRH(
                float _fWidth = 800.0f, float _fHeight = 600.0f,
                float _fZNear = 0.0f, float _fZFar = 1.0f );

        void makeTransformViewLH(
                const Vector3& _rPosition,
                const Vector3& _rDir,
                const Vector3& _rUp );
        void makeTransformViewRH(
                const Vector3& _rPosition,
                const Vector3& _rDir,
                const Vector3& _rUp );

        void makeTransformCoordinateSystem(
                const Vector3& _rOrigin,
                const Vector3& _rX,
                const Vector3& _rY,
                const Vector3& _rZ );


        //////////////////////////////////////////////////////////////////////
        // 행렬연산 ---
        //
        void multiply( const Matrix4& _rMatToMultiply );
        void transformVector3( Vector3& _rOutVecToTransform );
        void transformVector4( Vector4& _rOutVecToTransform );

        /*  00, 01, 02, 03    =   11, 12, 13, 14
            04, 05, 06, 07  = 21, 22, 23, 24
            08, 09, 10, 11  = 31, 32, 33, 34
            12, 13, 14, 15  = 41, 42, 43, 44   */

        float Entries[16];
    };
}


음... 나름 괜찮다고 생각중.. 특히 저 makeFromMultiply는 최적화를 잘해줘서 불필요한 임시객체 생성을 막을수 있을듯.

프로그래밍 게임개발, 프로그래밍, 행렬

클래스 생성과정을 강제하기

2007/05/04 00:02
매니저로 관리되는 클래스들의 생성과정을 Template Method 등으로 강제하면 '이곳에서 만들자'가 정해지므로 만드는것에만 집중할 수 있을것이다.

문제는 상속받아 쓰는 쪽에서 어떻게 이렇게 해라~ 를 강제시키는가인데.. 어떤면에서 상속가능한 객체를 힙에만 생성하기와 비슷한 문제이다.

일단 런타임시에 assert를 내는것으로 의도를 전하는데에 중심을 둔버전


class manager;
class managedInterface
{
friend class manager;
public:
managedInterface()
{
assert( CREATE_TEST == READY_TO_CREATE );
}
private:
enum CREATE_TEST_TYPE
{
READY_TO_CREATE = 0,
NEED_TO_SET
};
enum { MANAGED = 1 };
static CREATE_TEST_TYPE CREATE_TEST;
protected:
// 추상
virtual void OnCreate() = 0;
};
// 할당이 아닌 초기화를 위해서는 반드시 앞에 타입을 붙여준다.
managedInterface::CREATE_TEST_TYPE managedInterface::CREATE_TEST = managedInterface::NEED_TO_SET;

class managedImpl : public managedInterface
{
public:
// 속성
// managedImpl 하고 싶은것들
void runAndGun(){}

private:
// 구현
virtual void OnCreate(){}
};

class manager
{
public:
template
T* getManaged()
{
managedInterface::CREATE_TEST = managedInterface::READY_TO_CREATE;
T* pType = new T;
assert(T::MANAGED);
managedInterface::CREATE_TEST = managedInterface::NEED_TO_SET;
return pType;
}

static manager& getInstance()
{
static manager a;
return a;
}
};

int main()
{
//이렇게 하면 어써션이 뜬다.
//managedImpl* p = new managedImpl;

//이렇게 하면 어써션이 안뜬다.
managedImpl* p = manager::getInstance().getManaged();
managedImpl* pp = manager::getInstance().getManaged();

// 다시 이렇게 하면 어써션이 뜬다.
//managedImpl* pp = new managedImpl;
return 0;
}


어쨌든 의도는 전해질 것이고.. 코드또한 무겁지 않으니.. 이방법을 써먹어 봐야겠다.

프로그래밍 C/C++, 프로그래밍