페이지

2013년 6월 27일 목요일

AOT 컴파일러 와 Generic type 사용

사용 언어: C#

오늘 문제가 됐던 버그가 iOS버전으로 빌드를 하니 제네릭 클래스 타입을 사용하는 부분에서 crash가 생겼다. 안드로이드 버전에서는 문제가 안됐었는데 iOS버전에서만 crash가 나는 것이었다.

처음엔 원인을 몰라 당황했는데 유니티 트러블 슈팅 관련 문서를 읽어보니. 눈에 띄는 것이 있었다. "AOT": 현재 XCode에서 사용하는 컴파일 방식인데 이 방식은 JIT랑은 다른 형식의 컴파일 형태이다. 두 컴파일 방식에 대한 차이는 이곳 에서 확인 할 수 있다.

AOT방식에 사용제한이 있는데 generic parameter을 사용할때 explicit 한 type만 사용가능하다. 그래서 현재 프로젝트에서 generic type 을 generic container에 인자로 사용하자 크래쉬가 발생한 것이다.

해결한 방법으로는 현재 레퍼런스 타입을 받으므로 Systme.Object 를 인자로 받게 하고 다시 값을 얻어올때 generic type으로 캐스팅하도록 하였더니 이 문제를 해결 할 수 있었다.

문제가 됐던 코드 방식은 이것:

public class Test<T> : MonoBehaviour where T : Test<T>
{
        private Dictionary<int, T> m_test = new Dictionary<int, T>();

        public void foo()
       {
               Test<T> test = m_test[0];
       }    
}


해결 코드는 이것:

public class Test<T> : MonoBehaviour where T : Test<T>
{
        private Dictionary<int, System.Object> m_test = new Dictionary<int, System.Object>();

        public void foo()
       {
               Test<T> test = (Test<T>)m_test[0];
       }    
}


2013년 6월 22일 토요일

C++ 생성자 함수에서 virtual 함수를 호출 할 수 있을까?

이 글은 c++에서 왜 생성자 함수내에서 가상함수 호출을 하면 안되는가 대한 이유를 설명하는 글입니다. 아래 링크의 내용을 참고하였으나 제 마음대로 해석한 내용이므로 원본의 문장과는 다를 수 있습니다.

http://www.stroustrup.com/bs_faq2.html#vcall


답은 호출 할 수 있다. 하지만 사용하는데 조심해야 한다.
예상치 못한 일이 발생 할 수 있기 때문이다. 생성자 함수 내에서 가상함수를 호출하는 메카니즘은 사실 무의미하다. 왜냐면 파생클래스로부터의 오버라이딩이 아직 발생하지 않았기 때문이다. 모든 객체들은 "base before derived" 라는 기본 개념을 기반으로 설계 되어진다.

아래 예제 코드를 보자.

 #include<string>
 #include<iostream>
 using namespace std;

 class B {
 public:
  B(const string& ss) { cout << "B constructor\n"; f(ss); }
  virtual void f(const string&) { cout << "B::f\n";}
 };

 class D : public B {
 public:
  D(const string & ss) :B(ss) { cout << "D constructor\n";}
  void f(const string& ss) { cout << "D::f\n"; s = ss; }
 private:
  string s;
 };

 int main()
 {
  D d("Hello");
 }
 
출력 결과:
 B constructor
 B::f
 D constructor

D::f 가 아니다!! 어찌보면 너무 당연해보이지만.. 만약 B::B()로부터 D::f()가 호출되기 위해서 그 규칙이 다르다면 어떨지 생각해봐라. D::D() 생성자는 아직 실행되기도 전이기 때문에 D::f() 함수는 초기화 되지도 않은 string s를 할당하려고 할 것이다. 그 결과는 거의 곧 바로 크래쉬가 날 것이 뻔하다.

소멸자는 "derived class before base class" 을 기반으로 한다. 그래서 가상함수들은 생성자함수 내에서 처럼 동작한다. 오직 로컬 정의들만 사용되어진다. 그리고 현재 지워진 파생된 클래스를 건드리지 않기 위해 호출을 하지 않는다.

그동안 이것은 구현상의 제약사항으로 제안되어져 왔다. 하지만 그렇지 않다. 실제론 일반 함수내에서 가상함수를 호출하는 것처럼 생성자에서 호출하는 것은 안전하지 않은 규칙으로 여기는 것이 더 쉬울것이다.

C++ 생성자 함수가 가상함수가 될 수 있을까?

이 글은 c++에서 왜 생성자 가상함수가 허용되지 않는가에 대한 이유를 설명하는 글입니다.
아래 링크의 내용을 참고하였으나 제 마음대로 해석한 내용이므로 원본의 문장과는 다를 수 있습니다.

http://www.stroustrup.com/bs_faq2.html#virtual-ctor


답은 될 수 없다.

왜냐면 virtual 함수의 역할은 정해진 정보를 주기 위한 것이다.
OOP 에서 "virtual"의 기능은 확실한 타입을 모르는 객체의 인터페이스를 알게 해주기 위한 것이다.


하지만 하나의 객체를 생성하기 위해선 완전한 정보가 필요하다. 어떤 타입의 객체를 생성하려고 하는지 말이다. 그렇기 때문에 논리적으로 생성자 함수는 virtual 이 될 수가 없다.

간접적으로 생성자 함수를 virtual 로 호출하는 방법은 물론 있다.

아래의 예를 보자.


 struct F { // interface to object creation functions
  virtual A* make_an_A() const = 0;
  virtual B* make_a_B() const = 0;
 };

 void user(const F& fac)
 {
  A* p = fac.make_an_A(); // make an A of the appropriate type
  B* q = fac.make_a_B(); // make a B of the appropriate type
  // ...
 }

 struct FX : F {
  A* make_an_A() const { return new AX(); } // AX is derived from A
  B* make_a_B() const { return new BX(); } // BX is derived from B
 };

 struct FY : F {
  A* make_an_A() const { return new AY(); } // AY is derived from A
  B* make_a_B() const { return new BY(); } // BY is derived from B
 };

 int main()
 {
  FX x;
  FY y;
  user(x); // this user makes AXs and BXs
  user(y); // this user makes AYs and BYs

  user(FX()); // this user makes AXs and BXs
  user(FY()); // this user makes AYs and BYs
  // ...
 }

위 예제는 factory 패턴의 다른 형식이다. 여기서 요점은 user()가 AX 와 AY와 같은 클래스들에 대해 전혀 모른다는 것이다.