웹서핑 조금만 해도 나오는 싱글턴 패턴 소스.
평소처럼 사용하다가 싱글턴 클래스 상속과 싱글턴을 상속받은 부모 클래스를 다시 자식 클래스가
상속을 받아서 쓰는부분에 대해 궁금함이 생겼다.

일반적인 싱글턴 소스들
template< class T >
class CLmSingleton
{
public:
	static T	*getInstance() {
		if( !s_pInstance ) s_pInstance = new T();
		return s_pInstance;
	}
	static void	releaseInstance() {
		if( s_pInstance ) { delete s_pInstance; s_pInstance = NULL; }
	}

protected:
	static T *s_pInstance;
};

template< class T > T *CLmSingleton< T >::s_pInstance = 0;

//-------------------------------------------------------------- template< class T > class CLmSingleton2 : private T { public: static T* getInstance() { // if use in multi-thread env, should add lock here. static CLmSingleton2<t> s_Instance; return& s_Instance; } protected: CLmSingleton2() {} virtual ~CLmSingleton2() {} }; #define SINGLETON(p) CLmSingleton2<p>::getInstance()

상단에 있는 싱글턴 소스는
class A : public CLmSingleton<A>
{
public:
	A() {}
	virtual ~A() {}

	int a;
};

A testClass;			// 일반적인 정적 변수 선언이 가능.
testClass.a = 99;		// 이것은 단순히 testClass 라는 클래스 멤버변수의 값만 바뀐다.
A* pA0 = testClass.getInstance(); // pA0의 멤버변수 a의 값은 쓰레기 값
A* pA1 = A::getInstance();
A* pA2 = CLmSingleton<A>::getInstance();
// pA0, pA1, pA2 세 포인터의 주소는 동일하다.
pA2->a = 98;			// pA0, pA1, pA2의 멤버변수 a가 전부 갱신됨.

testClass 와 포인터들은 다른 주소를 갖는다. 다만 testClass.getInstance() 로 불러오는 인스턴스만은 동일하다.
각각의 인스턴스는 동일하지만 ( testClass != 포인터 ) 이기 때문에 싱글턴이라 부르기 어렵다.
또한, 싱글턴의 특징 중 하나인 외부로 공개하지 않는 생성자와 소멸자가 public이다.
결국, 저 클래스는 실수로라도 정적 변수로 선언을 하게 된다면 나중에 문제가 커질 가능성이 있다.

혹시나 해서 생성자와 소멸자를 protected 와 private로 했는데, 생성자에 접근을 하지 못해 컴파일 에러가 뜬다.


두번째 테스트로 상속을 받지 않고 싱글턴을 적용해 보았다.
class B
{
public:
	B() {}
	virtual ~B() {}

	int b;
};

B* pb0 = B::getInstance(); // 상속을 받지 않았으므로 B에는 getInstance()란 메소드가 없어 오류
B* pb1 = CLmSingleton<B>::getInstance(); // B 클래스라는 새로운 인스턴스 생성 성공.


마지막 테스트는 상단 소스중 CLmSingleton2 로 했다.
class C
{
public:			// protected 도 가능
	C() {}
	virtual ~C() {}

	int c;
};

class C : public CLmSingleton2<C> 로 선언 자체가 안된다. 소스를 보면 서로가 서로를 상속 받는 구조가 되어버린다.

두번째 싱글턴의 경우, 그 특징은 사용하려 명시한 클래스의 자식클래스로 상속이 된다. 첫번째는 싱글턴 자체가 부모클래스이고 사용하고 싶은 클래스는 자식클래스로 상속을 시키는 구조다.

이 싱글턴은 단지 아래 방법만이 가능하다.
C* pc0 = SINGLETON(C);
C* pc1 = SINGLETON(C);

두번째 싱글턴의 경우 클래스 생성자가 public이든 protected든 인스턴스가 생성이 된다. protected인데 생성이 되는 이유는 싱글턴이 부모의 생성/소멸자를 상속을 받았기에 내부에서는 부모를 참조할 수 있기 때문이다. private의 경우 생성/소멸자를 상속 시키지 않아 컴파일 에러가 난다.

//----------
그리고 두 싱글턴의 차이를 보자면 첫번재는 동적 인스턴스이다. 가져올때 없으면 new로 할당을 하고 존재한다면 이미 만들어진 인스턴스를 가져온다. 하지만 소멸자는 불리지 않아 인스턴스가 제대로 지워졌는지 확인이 안된다.
아니면 releaseInstance()를 호출하는 수 밖에 없다.

하지만 두번째 싱글턴은 정적 인스턴스이기 때문에 클래스 자체가 파괴가 되거나 프로그램이 종료가 된다면 메모리를 반환을 한다. 메모리 해제를 할 필요가 없다는 것이다.
그리고 첫번째 싱글턴처럼 인스턴스를 생성할 때 여러 방법이 불가능하다. 싱글턴 생성이나 인스턴스를 가져옴에 있어 통일성이 있다.
만약, 첫번째 싱글턴처럼 이렇게 만드나 저렇게 만드나 똑같은 인스턴스가 나온다면 인스턴스를 가져올 때 통일적이지 않아 나중가면 햇갈릴 가능성이 있는 것이다.

개인적으로 두번째 싱글턴이 좋다.

이 프로그램은 Sleep( 2000 )을 걸고! 퍼포먼스 카운터와 어셈블리 카운터와 타임겟타임을 비교해본것임.

순서대로 curTime 은 Performance Counter로 계산한 Elapsed time 이라고 할까요? 단위는 ns
part pc 는 ms단위의 Performance Counter,
curRe 는 앞에는 Performance Counter, 뒤에는 Assembly count 인데, 바로 전 상황과 현재의 차이.
asm pc 는 Assembly counter,
tgt.. timeGetTime
마지막 pc 는 순수한 Performance Counter의 값.

curRe 에서 보이듯이 어셈으로 테스트 했을땐 sleep( 2000 )에 맞게 딱딱 떨어졌는데,
퍼포먼스 카운트는 정확하게 2000으로 떨어지지가 않는다.( 요기도 역시 ns 단위 )

중간에 한번 변위가 엉망인 이유는 듀얼 코어.. 크흑.

확실히 퍼포먼스 카운터가 정밀도가 높아서 2초 단위로 찍어도 ns에서 나온 결과를 보면 수치가 조금씩 다르다.
멋쟁이 정밀도. 대신에 그 만큼 느리다는 단점은 널리 알려진 것이고.
근데.. 온라인 게임에서 쓰기에 ns단위까지 갈 필요가 있을까 라고 개인적으로 생각해본다.
그래서 개인적으로 timeGetTime을 주로 쓰는데 가끔 테스트로 GetTickCount 역시 사용하고. ㅋ
하지만 정밀도는 조금 버리는 한이 있더라도 역시 게임은 반응이 빨라야 재밌지. 함수 호출하는 시간을 조금이라도 더 아껴서
빠른 반응을 보인다면 약간의 딜레이정도는 무난하지 않을까 싶다. 그런의미에서 이제는 어셈 카운터가 원츄~!

참고로. performance counter 와 assembly counter 는 __int64형 timeGetTime은 DWORD형.
참고2. 어셈카운터는 ns 에서 0이 하나 더 붙어있는 값으로 나오더라 ㅋㅋ
C:\unigine\externs\bin;C:\unigine\lib;C:\unigine\bin;C:\Python26;"C:\Program Files\Microsoft DirectX SDK (October 2006)\Utilities\Bin\x86";%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;c:\Program Files\Microsoft SQL Server\90\Tools\binn;C:\Program Files\Microsoft SQL Server\90\Tools\binn;C:\Program Files\TortoiseSVN\bin;C:\Program Files\Common Files\Compuware;C:\Program Files\Microsoft SQL Server\90\Tools\binn\;C:\Program Files\Unigine\Evaluation Kit\tools\x86

위에는 실제로 사용하는 나의 path 경로 였었음.
헌데 scons를 쓰려고 하닌까 자꾸만 타이틀의 오류가 나면서 안되길래 한참을 고민했는데,
결국 찾아낸 문제의 부분!! "C:\Program Files\Microsoft DirectX SDK (October 2006)\Utilities\Bin\x86";
dx가 두가지가 깔려있는데 2006 8월이랑 2009 3월버전. 물론 2006 8월은 쓰이질 않고 지우는걸 깜박해서 그냥 냅뒀었는데, 그게 문제가 될줄이야.
요기까지는 잡설이었고.

요점! path 잡을때 ( ) 가 들어가면 링크가 엉망이 됨....
젠장 (October 2006)...
예상되지 않았다는 오류가 난다면 자신의 컴퓨터 path가 제대로 되어있는지 확인을 해보자. 혹시 몰라 검색해보니, path가 안 잡혀 있는 상태로 뭔가를 하려 할때도 난다고 하는데 그 부분은 아직 헤딩해보질 못해서 잘 모르겠다.

내컴터 오른쪽클릭 - 시스템 등록 정보 - 고급 - 환경변수 - 시스템변수 - path에 가면 자신의 윈도우 path를 수정할 수 있다.
글타고 막 수정하진 말자. 언제나 백업을 위주로~!

포스팅 할 예정이 아니어서 인증샷찍는걸 깜박..;
[-] Collapse
#include <iostream>
#include <ctime>
#include <string>

#define _WIN32_WINNT 0x0501

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>

using boost::asio::ip::tcp;

std::string make_daytime_string()
{
    std::time_t now = std::time( 0 );
    return std::ctime( &now );
}

class Ctcp_connection : public boost::enable_shared_from_this< Ctcp_connection >
{
public:
    typedef boost::shared_ptr< Ctcp_connection > pointer;

    static pointer create( boost::asio::io_service &io )
    {
        return pointer( new Ctcp_connection( io ) );
    }

    tcp::socket &socket()
    {
        return m_socket;
    }

    void start()
    {
        m_message = make_daytime_string();
        // # handle_write 함수에서 매개변수 2가지를 사용할 필요가 없다. 함수 다이어트
//  boost::asio::async_write( m_socket, boost::asio::buffer( m_message ),
//  boost::bind( &Ctcp_connection::handle_write, shared_from_this(),
//  boost::asio::placeholders::error,
//  boost::asio::placeholders::bytes_transferred) );

        std::cout << "5-1 prepare async write" << std::endl;

        boost::asio::async_write( m_socket, boost::asio::buffer( m_message ),
            boost::bind( &Ctcp_connection::handle_write, shared_from_this() ) );

        std::cout << "5-2 send daytime to client and binding" << std::endl;
    }

private:
    Ctcp_connection( boost::asio::io_service &io )
        : m_socket( io )
    { }

// void handle_write( boost::asio::placeholders::error_code & /*error*/,
//  size_t /*bytes_transferred*/ )
// { }

    void handle_write()
    { }

    tcp::socket     m_socket;
    std::string     m_message;      // char[] or std::vector도 사용 가능
};

class Ctcp_server
{
public:
    Ctcp_server( boost::asio::io_service &io )
        : m_acceptor( io, tcp::endpoint( tcp::v4(), 13 ) )
    {
        std::cout << "1 call creater" << std::endl;
        start_accept();
    }

private:
    void start_accept()
    {
        std::cout << "2 call start accept func" << std::endl;

        Ctcp_connection::pointer new_connection = Ctcp_connection::create( m_acceptor.io_service() );

        std::cout << "3 prepare async" << std::endl;

        m_acceptor.async_accept( new_connection->socket(), boost::bind( &Ctcp_server::handle_accept,
            this, new_connection, boost::asio::placeholders::error ) );

        std::cout << "4 async complete and binding\n --Wait Connect" << std::endl;
    }

    void handle_accept( Ctcp_connection::pointer new_connection, const boost::system::error_code &error )
    {
        if( !error )
        {
            std::cout << "5 start handle_accept" << std::endl;
            new_connection->start();
            std::cout << "6 call start_accept func" << std::endl;
            start_accept();
        }
    }

    tcp::acceptor   m_acceptor;
};

int main()
{
    try
    {
        boost::asio::io_service io;
        Ctcp_server server( io );
        io.run();
    }
    catch( std::exception &e )
    {        std::cerr << e.what() << std::endl;
    }

    return 0;
}
출력된 결과


중간 --Wait Connect 라는 부분은 Client로 접속하기 전까지 대기 상태로 유지된다.
5번부터 4번까지( 5 -> 5-1 -> 5-2 -> 6 -> 2 -> 3 -> 4 ) Client 가 접속될때 실행되는 부분이다.

소스를 조금 분석하자면,
m_acceptor.async_accept( 소켓, bind( 연결할 함수 포인터 ) );
프로그램이 흐르다가 이 부분을 만나면 무작정 대기에 빠진다. 물론 async이기 때문에 다른 작업이 가능하다.
상단의 소스에는 thread를 쓰고 있지 않기에 무한대기 상태에 빠진것처럼 보인다.

다시 말하자면, 명시된 소켓으로 Client가 접속이 될때까지 binding된 콜백함수를 호출하지 않는 것이다.

클라이언가 접속이 되면( 즉, accept 가 되면) 바인딩된 함수로 호출이 되는것이다. 그 전까지는 함수를 호출하지 않는다. )

5-1과 5-2 사이에도 async_write 함수가 있는데 이것 역시 같은 맥락일 것이라 예상된다.이 부분은 나중에 thread를 추가 하고 나서 다시 한번 테스트. 하지만, asio의 흐름상 비슷할거라 생각된다. 명시된 소켓으로 패킷을 날리는데, 다 날라갈때까지 대기를 하고 다 날라간 이후엔 binding된 콜백함수를 호출한다.

소스에는 호출되는 콜백함수( Ctcp_connection::handle_write )는 내부가 비어 있기 때문에 아무 처리를 안 한다.
이론적으로 흐름을 잡자면, handle_write를 5-3이라 한다면, 5-1 -> 5-3 -> 5-2 이 될것이다.
만약 보낼 패킷의 양이 많아 async 상태가 된다면 5-1 -> 5-2 -> 처리가 끝날때 까지그외에 다른 부분 -> 5-3 이 될것이다.

하나의 Client가 접속을 하고 daytime을 받아간후 6번을 지나면 다시 2번으로 돌아간다.
여기서 다시 async_accept에서 대기.

요기까지 개인적으로 분석한 Asio Tutorial 중 Daytime.3 - An asynchronous TCP daytime server 부분이다.

post script.
cout으로 번호랑 주석 비스므리하게 출력한건 분석하기 위해 임의로 추가한 부분입니다.

'Programming > Boost' 카테고리의 다른 글

asio -- 1  (0) 2009.12.01
Bind 공부중인거 정리. < 01 >  (0) 2009.08.20
Signal 공부중인거 정리. < 04 >  (0) 2009.08.20
Signal 공부중인거 정리. < 03 >  (0) 2009.08.20
Signal 공부중인거 정리. < 02 > - 1  (0) 2009.08.19
asio -- 1 Programming/Boost 2009. 12. 1. 12:23
[-] Collapse
#define _WIN32_WINNT 0x0501

#include <iostream>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

int main()
{
    boost::asio::io_service io;
    boost::asio::deadline_timer t( io, boost::posix_time::seconds( 10 ) );

    std::cout << "test" << std::endl;

    t.wait();       // 10초 동안 대기. blocking

    std::cout << "ttttt" << std::endl;
}
블록모드. 10초동안 대기를 명하고, wait를 만나게 되면 10초동안 말 그대로 wait(대기) 한다. 만약 wait 명령어를 호출 해주지 않는다면, 그냥 패스~

'Programming > Boost' 카테고리의 다른 글

asio - TCP_server( async )  (0) 2009.12.01
Bind 공부중인거 정리. < 01 >  (0) 2009.08.20
Signal 공부중인거 정리. < 04 >  (0) 2009.08.20
Signal 공부중인거 정리. < 03 >  (0) 2009.08.20
Signal 공부중인거 정리. < 02 > - 1  (0) 2009.08.19
std::map< int, int > Map1;
이런녀석이 있다고 할때 Map1[1], Map1[2] 처럼 접근이 가능하다. 물론 리턴값은 first에 맞는 second

그런데 std::map< int, int > *pMap2;
이렇게 선언되어있는 녀석이 있다면 pMap2[1], pMap2[2] 의 접근이 불가능하다.
이유인 즉슨!!

일반 map 에서 정의되어 있는 [] 오퍼레이터가 제대로 작동하지 않아서이다.
포인터 map에서의 []는 int *pI = new int[10]; 에서의 pI[1] 와 같은 의미.

결국 map 이라는 녀석이 할당된 메모리 주소를 쭉 찾아가다보니 당연히 first 값을 넣어도 second가 나오지 않는다.

( 확실한건 아니고 나의 가설일뿐 ㅎㅎ )

그렇다면 포인터로 선언한 map은 쓸수 없는가?
당연히 사용이 가능하다. 세가지 방법중 맘에 드는걸로 쓰는걸 추천~

1. 레퍼런스를 만들어 참조한다.
std::map< int, int > &refMap = *pMap2;
- 장점은 일반 map 사용하듯이 쓰면 된다.
- 단점은 주소 복사연산이기 때문에 변수 생성시에만 가능하다.
std::map< int, int > &refMap; << 선언 에러
std::map< int, int > refMap;
&refMap = *pMap2; << 말도 안되는 연산. 당연히 에러

2. 주소를 열심히 찾아가는 방법을 쓴다.
int iSecond = (*pMap2)[0];
- 장점은 레퍼런스가 아니니 때문에 어디서든 map 포인터에서 second의 값을 불러올 수 있다.
- 단점은 아직 못 찾았는데, 개인적으로 미관상 소스가 이쁘지 않다! ㅋㅋㅋ

3. 이터레이터를 사용한다면 아무 문제없다.
포인터든 아니든 이터레이터는 진리.

post script.
map은 [] 오퍼레이터 내부에서 find 와 insert를 돌린다. find로 있으면 second리턴 없으면 insert
( 이걸 모른다면 map 공부를 다시 하시길.. ㅋ )
위에 쓴 예시는 내부에 값이 있다는 전제하에 쓴 글임니당.

vector나 list에서도 같은 문제가 발생하겠죵? 테스트를 안해봐서 이건 모릅니다! ㅋ