1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
MatchMother::MatchMother(std::weak_ptr<BroadCaster> broadCaster)
{
	// match mother is interested in match request packet
	// session id -1 means all sessions
	_broadCaster = broadCaster;
	if (auto& spt = _broadCaster.lock())
	{
		spt->Subscribe(Subject{ Packets::ID_PACKET_MATCH_REQ,ALL_SESSION }, shared_from_this());
	}
	else
	{
		LibLogger::cError << "weak ptr is expired" << std::endl;
	}
}



위 코드 8번라인에서 자꾸 죽어서 타고 타고 들어가 보았다.


shared_from_this()는 단순히 enable_shared_from_this<T>의 멤버인 _Wptr을 shared_ptr로 캐스팅하여 반환한다.


그런데 enable_shared_from_this<T>의 멤버 _Wptr은 enable_shared_from_this<T>의 생성자에서 셋팅되는 것이 아니라, std::make_shared()에서 셋팅된다.


std::make_shared<MatchMother>()의 내부에서, _Wptr 셋팅이 MatchMother 생성자보다 뒤에서 일어난다.


따라서 생성자 내부에서 shared_from_this()를 호출하는 것은, 아직 셋팅되지 않은 _Wptr을 호출하는 행위이고, 따라서 죽을 수 밖에 없는 것.

Posted by RPG만들기XP
,
Posted by RPG만들기XP
,

C++에서 보통은 헤더파일에는 외부 클래스를 전방선언만 해두고, .cpp파일에서 구현부를 가져다 쓰는 것이 일반적이다.(순환참조를 막기 위해)


헌데 템플릿 멤버함수를 선언하려니, 템플릿 함수는 구현부와 선언부를 헤더와 cpp파일에 분리할 수 없다.(어쩌고 저쩌고 사정으로 인해.)

따라서 전방선언해둔 클래스의 기능을 실제로 호출하는 코드를 헤더에 정의하게 되는데, 비주얼스튜디오는 알아서 잘 컴파일해주지만, 몇몇 컴파일러에서는 깐깐하게도 "정의는 없고 선언만 있는 클래스인데 멤버함수를 호출하셨네요! 컴파일 못해요!" 해버린다.

어떻게 해결해야 할까..


#pragma once

class UnitState;

//Unit 자식 클래스를 위한 CREATE_FUNC
#define CREATE_FUNC_UNIT(__TYPE__) \
static __TYPE__* create(PLAYER_KIND ownerPlayer) \
{ \
    __TYPE__ *pRet = new(std::nothrow) __TYPE__(); \
    if (pRet && pRet->init(ownerPlayer)) \
    { \
        pRet->autorelease(); \
        pRet->scheduleUpdate();\
        return pRet; \
    } \
    else \
    { \
        delete pRet; \
        pRet = nullptr; \
        return nullptr; \
    } \
}

class Unit :
    public Sprite
{
public:
    bool init(PLAYER_KIND playerKind);
    CREATE_FUNC_UNIT(Unit)
    virtual void        update(float delta);
    template<typename T_STATE>
    void                changeState()
    {
        if (_state)
        {
            this->getState()->EndState(this);
            this->removeComponent(_state);
        }
        auto state = T_STATE::create();
        this->setState(state);
        this->getState()->StartState(this);//문제가 되는 부분. 선언만 해놓은 클래스(UnitState)의 구현부를 가져다 쓰고 있다.
        this->addComponent(state);
    }

    CC_SYNTHESIZE(UnitState*, _state, State);
    CC_SYNTHESIZE(std::string, _unitName, UnitName);
    CC_SYNTHESIZE(float, _attackRange, AttackRange);
    CC_SYNTHESIZE(float, _attackSpeed, AttackSpeed);
    CC_SYNTHESIZE(float, _moveSpeed, MoveSpeed);
    CC_SYNTHESIZE(Unit*, _attackTarget, AttackTarget);
    CC_SYNTHESIZE_READONLY(PLAYER_KIND, _ownerPlayer, OwnerPlayer);
    Unit*               scanNearestTarget();
private:
};

ㄴ 문제의 코드.


공부좀 해보고 해결하면 다시 정리해서 올려야겠다.


* 일단은 문제의 UnitState.h에서 Unit.h를 include하지 않아도 되게 코드를 수정해서 해결하였다.

Posted by RPG만들기XP
,

Item 2: Understand auto type deduction.

auto 타입 추론에 대해 알아보자.


item 1에서 템플릿 타입 추론에 대해 공부했다면, auto 타입 추론에 대한 대부분을 이미 알고 있는 셈이다.

한 가지 희한한 예외를 빼면, auto 타입 추론이 곧 템플릿 타입 추론이기 때문이다.

이는 템플릿 타입 추론과 auto 타입 추론 사이에는 직접적 대응 관계가 존재해, 알고리즘적으로 상호변환이 가능하기 때문이다.

template<typename T>
void f(ParamType param);

f(expr);

위와 같은 일반적인 함수 템플릿과 호출에 대한 타입 추론은, 컴파일러가 expr을 이용해서 T와 ParamType의 타입을 추론해낸다.

auto 타입 추론은 템플릿의 T와 동일한 역할을 하며, 변수의 형식 지정자(type specifier)는 ParamType과 동일한 역할을 한다.

도대체 무슨말일까?? 예제를 보자.

auto x = 27;//type specifier는 그냥 auto 자체이다.

const auto cx = x;//type specifier는 const auto.

const auto& rx = x;//type specifier는 const auto&

위 케이스에서 컴파일러는 마치 다음과 같은 구문이 존재하는 것처럼 행동한다.

즉 : 

template<typename T>
void func_for_x(T param);

func_for_x(27);

template<typename T>
void func_for_cx(const T param);

func_for_cx(x);

template<typename T>
void func_for_rx(const T& param);

func_for_rx(x);

이것과 똑같이 작동한다는 것이다! 한 방에 이해가 되었다!


그에 따라 auto 타입 추론은 템플릿에서의 경우의 세 가지 case와 똑같이 작동한다.(배열, 함수 이름이 포인터로 붕괴하는 방식도 같다.)

하지만 유일하게 다른 점이 있다.

어떤 경우인지 예를 살펴 보자.

//C++98에서는 다음 구문들을 사용할 수 있다.
int x1 = 27;
int x2(27);

//위에 더하여, C++11에서의 균일 초기화(uniform initialization)를 이용하여 다음과 같은 구문도 사용 가능하다.
int x3 = {27};
int x4{27};

위 네 가지 구문 모두 결과적으로 값이 27인 int가 생긴다는 점은 동일하다.

그런데, 고정된 타입 대신 auto를 이용하여 변수를 선언하는 데에는 몇 가지 장점이 있다.(이유는 항목5에 나온다고 함.)

따라서 int를 auto로 대체해 보자.

auto x1 = 27;
auto x2(27);
auto x3 = {27};
auto x4{27};

이 선언들은 모두 문제없이 컴파일되지만, 이전 버전과는 의미가 좀 달라진다.

x3, x4는 int가 아니라 std::initializer_list<int> 타입의 변수를 선언한다(?!).

이유는 다음과 같다.

auto로 선언된 변수의 초기값이 중괄호로 감싸인 형태이면, 추론되는 타입은 std::initializer_list이다.(위 case는 원소가 한개인 경우)

auto x5 = {1,2,3.0f};//따라서 이 코드는 당연히 컴파일이 안된다.

이 경우가 템플릿 타입 추론과 다르게 작동하는 부분은, 같은 템플릿 함수에 동일한 중괄호 초기값을 전달하면 컴파일이 거부된다는 점이다.

auto x = {11,23,9};//당연히 잘됨. 타입은 std::initializer_list<int>

template<typename T>
void f(T param);

f({11,23,9});//컴파일 오류! T에대한 타입추론 불가

정리하면 : auto는 중괄호 초기값이 std::initializer_list를 의미한다고 가정하지만, 템플릿 추론은 그렇지 않다.

이유는 아무도 모른다(책에도 써있다..)

결론 : auto의 초기화를 균일 초기화 할때는 std::initializer_list 타입이라는 것을 염두해 두자.


이상은 C++11까지의 내용이지만, C++14에서는 알아야 할 것이 더 남아있다.

  1. C++14에서는 함수의 리턴 타입을 auto로 지정해서 컴파일러가 추론하게 할 수 있다.
  2. C++14에서는 람다의 파라미터로 auto를 사용할 수 있다.

하지만, 위의 두 용법에서의 auto는 auto 타입 추론이 아니라 item 1에서 배웠던 템플릿 타입 추론의 규칙들이 적용된다.

따라서,

auto createInitList()
{
    return {1,2,3}; //안됨
}

std::vector<int> v;

auto resetV = [&v](const auto& newVal) { v = newVal;};
resetV({1,2,3});//안됨

위의 두 경우는 컴파일이 실패한다.


Things to Remember

  • auto 타입추론은 대체로 템플릿 타입추론과 같지만, auto 타입추론은 중괄호 초기값이 std::initializer_list를 나타내고 템플릿에서는 그렇지 않다.
  • 함수의 리턴값이나 람다 매개변수에 쓰인 auto는 auto가 아니라 템플릿에서의 타입추론이 적용된다.


'C++ > Effective Modern C++' 카테고리의 다른 글

Item 1: Understand template type deduction.  (0) 2016.03.24
Posted by RPG만들기XP
,

Item 1: Understand template type deduction.

템플릿 타입 추론에 대하여 알아보자.


c++ 11에서 컴파일러의 auto에 대한 타입 추론은 템플릿에 대한 타입 추론을 기반으로 작동한다.

단, 그러한 규칙들이 auto의 context의 적용될 때에는 템플릿의 경우에 비해 덜 직관적인 경우가 있다.

따라서, auto를 잘 활용하려면 auto가 대체 어떻게 돌아가는 놈인지 확실히 이해하고 있어야 한다.


template<typename T>
void f(ParamType param);

함수 템플릿의 선언은 대충 위와 같이 쓴다.


그리고 호출하는 곳은 대체로

f(expr);//어떤 표현식으로 f를 호출

이런 느낌..


컴파일 타임에 컴파일러는 expr을 통해 두 가지의 타입을 추론한다.

하나는 타입 T, 하나는 ParamType의 타입이다.

(왜 따로냐 하면, ParamType이 대개 const T& 등등 수식어들이 붙기 때문)


예를 들어,

template<typename T>
coid f(const T& param);

int x = 0;
f(x);

위에서 T는 int로 추론되고, ParamType은 const int&로 추론된다.(당연)


T가 expr과 같은 타입일 것이라고 기대하는 것은 당연하나, T의 타입 추론은 expr뿐만 아니라 ParamType에 의해서도 영향을 받는다.

그 형태에 따라 세 가지 케이스로 나뉘는데,

  1. ParamType이 포인터 또는 reference 타입이지만 universal reference는 아닌 경우
  2. ParamType이 universal reference일 경우
  3. ParamType이 포인터도 아니고 참조도 아닌 경우
이렇게 세 가지이다.

template<typename T>
void f(ParamType param);

f(expr);

case 1 : ParamType이 포인터 또는 reference지만, universal reference는 아님

가장 간단한 케이스.

  1. 만약 expr이 reference이면 reference 부분을 무시한다.
  2. 그 다음 expr의 타입을 Paramtype에 대해 pattern-matching 방식으로 대응시켜 T의 타입을 결정한다.
결과는 - 
template<typename T>
void f(T& param);

int x = 27;
const int cx = x;
const int& rx = x;

f(x); //T는 int, param은 int&
f(cx);//T는 const int, param은 const int&
f(rx);//T는 const int, param은 const int&

f의 매개변수 타입을 const T&로 바꾸면 객체에 대한 const성의 기대 충족을 param에서 채워줄 수 있으므로, const가 T한테까지 가지 않아도 된다.

결과는 - 

template<typename T>
void f(const T& param);

int x = 27;
const int cx = x;
const int& rx = x;

f(x); //T는 int, param은 const int&
f(cx);//T는 int, param은 const int&
f(rx);//T는 int, param은 const int&

param이 reference가 아니라 포인터라도 타입 추론은 똑같이 돌아간다.

결과는 - 

template<typename T>
void f(T* param);

int x = 27;
const int* px = &x;

f(&x);//T는 int, param은 int*
f(px);//T는 const int, param은 const int*


case 2 : ParamType이 universal reference임

universal reference가 뭔지 나는 모른다. (항목24에 나온다고 함)

일단은 대충 이렇다고 한다.

  • expr이 L-value이면, T와 ParamType 둘 다 L-value reference로 추론됨.
    • 웃긴점 1 : 템플릿 타입 추론에서 T가 reference로 추론되는 경우는 이 case가 유일.
    • 웃긴점 2 : ParamType은 겉으로 보기에 R-value reference일 것 처럼 생겼지만, 추론되는 결과 타입은 L-value이다.
  • expr이 R-value이면, case 1을 따름
무슨 소리인지 모르겠지만, 예시를 보자.
template<typename T>
void f(T&& param);

int x = 27;
const int cx = x;
const int& rx = x;

f(x);//x는 lvalue, T는int&, param도 int&
f(cx);//cx는 lvalue, T는 const int&, param도 const int&
f(rx);//rx는 lvalue, T는 const int&, param도 const int&
f(27);//27은 rvalue, T는 int, param은 int&&

이유는 잘 모르겠으나, 일단 외우기로 하였다.

왜 이렇게 되는지에 대한 이뉴는 item 24에 나온다고 하니 일단은 외우자.


case 3 : ParamType이 포인터도 아니고 reference도 아님

그냥 byVal로 전달되는 케이스이다.

param이 새로 만들어지는 복사 객체라는 사실 때문에, 다음 규칙이 적용된다.

  1. case 1처럼, expr이 reference 타입이면 reference부분은 무시한다.(호출이 되긴 되나보다)
  2. expr의 참조성을 무시한 후, expr에 const성이 있으면 그 const성 역시 무시한다.
    만약 volatile이면 그것도 무시한다.(엥??왜지?? - 항목40에 나온다고 함)

예시를 보자.

template<typename T>
void f(T param);

int x = 27;
const int cx = x;
const int& rx = x;

//다음 세 가지 case 모두 T와 param 둘 다 int
f(x);
f(cx);
f(rx);


보고 가야 할 예시도 있다.

template<typename T>
void f(T param);//값복사

//const char에 대한 const형 포인터
const char* const ptr = "Fun with pointers";

f(ptr)//expr은 const char* const이다.

위 예시에서 expr은 const char* const이고, const형 char에 대한 const형 "포인터"이다.

결국 메모리 주소(포인터의 값)를 전달하게 되고, 그에 대한 const는 무시된다.

하지만, 가리키고 있던 대상(const char)의 const성은 포인터의 값복사 여부와는 아무 상관이 없으므로, param은 const char*이 된다.


모든 case에 대하여 알아본 것 같지만, 틈새 케이스가 있다.

그것은 바로바로 배열타입..(포인터 타입과 같은 게 아니었다?!)

배열과 포인터를 맞바꿔 쓸 수 있는 것처럼 보이게 하는 주 원인은, 많은 경우에서 배열타입이 배열의 첫 원소를 가리키는 포인터로 붕괴(decay)한다는 점이다.

const char name[] = "Gogiga meokgo sipda";//name은 const char*이 아니라 const char[13]이다.

const char * ptrToName = name;//배열이 포인터로 붕괴된다.

전혀 몰랐던 사실이다. 배열->포인터 붕괴 규칙때문에 오류 없이 잘 컴파일된다.

하지만 byVal로 매개변수를 받는 템플릿에 배열을 넣으면 어떻게 될까?

//(위 코드에 이어서)
template<typename T>
void f(T param);

f(name);//과연??

우선, 배열 타입의 매개변수라는 것은 없다는 점부터 짚고 넘어가자.(배열 타입이라는 게 있는지도 몰랐다.

void some_func(int param[]);

그런데도 위 구문이 적법한 이유는, 이 경우 배열 선언이 하나의 포인터 선언으로 취급되기 때문.(int* param과 완전히 같은 의미이다.)

위 케이스와 같이, 배열타입을 byVal로 전달하는 템플릿 함수에 매개변수로 넣으면, 배열 타입이 포인터형으로 추론된다.

즉, name은 배열이지만, T는 const char*가 되는 것이다.

하지만 여기서 신기한 요령이 있는데, 배열 타입 매개변수는 불가능하지만, 배열 타입에 대한 참조 타입 매개변수는 가능하다는 것이다.

따라서,

template<typename T> void f(T& param); const char name[] = "123..."; f(name);

요렇게는 가능하다는 것이다. 그러면 T는 배열의 실제 타입이 된다(!!!).

배열 타입은 배열의 크기를 포함하므로, 위 케이스에서 T는 const char[7]로 추론되고, param은 const char (&)[7]이 될 것이다.(해괴한 문법...)

이를 활용하면 신기하게도 배열의 원소 개수를 추론하는 템플릿을 만들 수 있다.

//배열의 크기를 컴파일 시점 상수로 리턴하는 템플릿 함수
template<typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) noexcept
{
    return N;
}

constexpr로 선언하면 함수호출의 결과를 컴파일타임에 사용할 수 있게 된다고 한다(item 15에서 다시 설명).

문법이 낯설고 해괴하지만, 어떻게 돌아가는 건지 꼼꼼히 확인해 볼 필요가 있겠다.


위에서 설명한 배열에 대한 추론에 관련한 모든 것이 함수 타입에 대하여도 똑같이 적용된다.

똑같은 원리에 의해서 함수 타입이 함수 포인터로 붕괴 가능하다.(함수 타입이 있었다니..)

실제 응용에서 신경 쓸 부분은 아니라고 하나, 알아둬서 나쁠 것은 없겠다.



Things to Remember

  • 템플릿 타입추론 도중에 reference 타입의 인자들은 const성이 무시된다.
  • universal reference에 대한 타입추론에서 L-value들은 특별하게 취급된다.
  • byVal 방식의 매개변수에 대한 타입추론에서 const와 volitile은 무시된다.
  • 템플릿 타입추론 과정에서 배열(함수)형식의 인수는 포인터로 붕괴한다.
    단, 배열(함수)에 대한 reference일 경우에는 붕괴하지 않는다.


'C++ > Effective Modern C++' 카테고리의 다른 글

Item 2: Understand auto type deduction.  (0) 2016.03.24
Posted by RPG만들기XP
,