(!) 주의 : 이 글은 C++ 초보자용입니다. 아이폰용 cocos2d를 사용하다가 넘어온분들이 C++에 익숙치 않아 헤메는 분들을 위한 글입니다.



cocos2d-x 3.0 beta2에서 (beta1에서도 동일) 변경점 중의 하나가 몇몇 클래스의 생성자와 init관련 멤버함수들이 protected 로 지정되었다는 점입니다.


즉, 이제는 기본적으로 아래와 같이 수동(?)으로 스프라이트를 생성하려고 하면,


cocos2d::Sprite *pMySprite = new cocos2d::Sprite;

pMySprite->initWithFile ("...........");


빌드시 cocos2d::Sprite의 protected 생성자를 호출했다는 에러가 발생합니다.


Calling a protected constructor of class 'cocos2d::Sprite'


뜬금없이 이게 무슨 ....


그래서 엔진의 cocos2d::Sprite의 소스를 살펴 보니...


.

.

.

protected:


    Sprite(void);

    virtual ~Sprite(void);


    virtual bool init(void);

    virtual bool initWithTexture(Texture2D *texture);

    virtual bool initWithTexture(Texture2D *texture, const Rect& rect);

    virtual bool initWithTexture(Texture2D *texture, const Rect& rect, bool rotated);

.

.

.



네, 역시나 protected 안으로 들어가 버렸군요. 이제는 기본적으로는 create류의 컨비니언스 계열 멤버함수로만 스프라이트를 생성하라는 뜻인가 봅니다. (create류를 사용하면, autorelease를 사용하게 됩니다.)


일반적인 경우는 create류를 사용하여 오토릴리즈풀을 이용한 메모리 관에 맡겨도 되겠지만, 경우에 따라 수동으로 메모리 관리를 해야 할 경우도 있습니다.


더군다나 라이프사이클이 좀 길고, 외부에서도 접근해야 할 경우가 가끔 있습니다. 물론 이런 경우에 retain 을 수동으로 호출하여 release count를 하나 늘려 주어도 되기는 합니다.


하지만, 천재(?) 프로그래머가 아닌 이상은 수동으로 관리할 때에는 안전장치를 두고 싶을 겁니다.


그 안전장치란, 별건 아닙니다만 해당 멤버 변수가 널인지(Null pointer) 확인해보는 것이지요.


문제는, autorelease에 맡겨두었을 경우 release 하고 나서 해당 변수를 Null 상태로 알아서 만들어 주느냐가 문제가 될 수 있겠네요. 


그럼 cocos2d-x 엔진의 release 소스를 또 봅시다.


void Object::release()

{

    CCASSERT(_referenceCount > 0, "reference count should greater than 0");

    --_referenceCount;

    

    if (_referenceCount == 0)

    {

#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)

        auto poolManager = PoolManager::getInstance();

        if (!poolManager->getCurrentPool()->isClearing() && poolManager->isObjectInPools(this))

        {

.

.

.

            CCASSERT(false, "The reference shouldn't be 0 because it is still in autorelease pool.");

        }

#endif

        delete this;

    }

}


아아...이런!?!? 그냥 delete 만 해줍니다. 널 처리는 해주지 않네요. --;


이게 왜 문제가 되냐면, C++에서는 메모리 해제된 변수에 접근하게 되면 에러가 발생하기 때문에, 소스코드가 복잡해지고 외부에서 접근해야 할 경우가 생길때 해당 변수가 해제된 상태인지 확인하기 위해 Null 상태인지를 체크하게 됩니다. 하지만, 메모리 해제 후 변수에 직접적으로 NULL을 대입해주지 않으면, 해당 변수는 엉뚱한 메모리를 가르키고 있기 때문에 정상 동작을 하지 않습니다.


(참고로, Objective C에서 넘어온 경우, Objective C에서는 null pointer에 접근하면 아무일도 일어나지 않지만(언어 스펙임), C++에서는 null pointer에 접근하면 프로그램이 죽습니다.)


즉, 실제로 메모리는 해제 되었는데, 해제된 변수인지 아닌지 분별할 수가 없게 되는거죠. 그래서 보통은 delete 한 후에 NULL을 대입해 줍니다.


자, 그럼 이걸 어떻게 해결할 것이냐....


제일 간단한 방법은 엔진 소스를 고치면 됩니다. 맨 위에 protected 안에 있는 생성자와 init계열 멤버 함수들의 선언들을 public 쪽으로 옮기면 되겠지요.


하지만, 저는 엔진 소스를 수정하는것을 원치 않습니다. 왜냐면, 엔진 자체를 수정하게 되면, 엔진이 업데이트 될때마다 골치 아파지기 때문입니다.


그럼, 다음 해결 방법은? 그냥 상속을 이용하면 됩니다. 


저는 아래와 같이 소스를 만들었습니다.


MyC2DXSprite.h

#ifndef __MY2DXSprite__

#define __MY2DXSprite__


#include "cocos2d.h"



namespace myc2dx {

class Sprite : public cocos2d::Sprite {

public:

Sprite ();

virtual ~Sprite ();

virtual bool InitWithFile (const std::string &filename);

};

}


#endif /* defined(__MY2DXSprite__) */



MyC2DXSprite.Cpp



#include "My2DXSprite.h"



namespace myc2dx {


Sprite::Sprite ()

{

// CCLOG ("myc2dx::Sprite constructor called!");


}



Sprite::~Sprite ()

{

// CCLOG ("myc2dx::Sprite destructor called!");

}


// 아래 처럼 필요한 init 함수 추가

bool Sprite::InitWithFile(const std::string &filename)

{

return initWithFile(filename);    // cocos2d::Sprite의 initWithFile 호출

}

.

.

.

}


너무 쉽죠? 저도 써놓고 보니 너무 간단해서 민망하네요.


사용할때는 이제 예전 처럼 그냥 사용하면 됩니다.


auto mySprite = new myc2dx::Sprite;

mySprite->initWithFile (".........");


이런식으로 말이죠.


글에서는 안적었지만, 실제로 엔진소스 생성자와 소멸자에 로그를 찍어보았는데, 아무 문제 없지 제대로 호출되는 것도 확인했습니다.


아래는 참고로 로그를 찍어본 것입니다. (로그를 위해 엔진소스 살짝 바꿨다가 복구함)


cocos2d: I want to call constructor!!! [+]

cocos2d: cocos2d::Sprite constructor called!

cocos2d: mc2dx::Sprite constructor called!

cocos2d: I want to call constructor!!! [-]





cocos2d: I want to call destructor!!! [+]

cocos2d: mc2dx::Sprite destructor called!

cocos2d: cocos2d::Sprite destructor called!

cocos2d: I want to call destructor!!! [-]





끝.



Posted by 날개

댓글을 달아 주세요

티스토리 툴바