2010. 7. 20. 23:01

'형식 캐스팅' : ...로의 변환이 있지만 액세스할 수 없습니다.


CTest* a;
CParent* p = (CParent*)a;
등과 같이 부모클래스로 캐스팅할때..

아래처럼 실수로 쓰지 상속관계를 정의하지 않은 경우에 private으로 자동설정되어 접근할 수 없어 발생하는 에러다. 간단히 상속관계를 public으로 바꿔주면 해결된다.

CTest : CParent
{
};
를..

CTest : public CParent
{
};
로 바꿔주면 된다.


애초에 private 상속은 두가지 규칙을 가진다. 아래는 Effective C++에 나온 부분을 발췌한것.
첫번째로 부모클래스로의 변환불가
두번째로 부모클래스의 멤버들이 자식클래스의 private 멤버가 된다.(출처 Effective C++)
기타..

따라서 저런 에러가 발생하는 것이다.
2010. 6. 15. 04:09

기본 클래스를 정의하지 않았습니다.


이 에러는 존재하지 않는(컴파일러의 입장에서) 클래스를 상속한 경우에 발생하는 오류메시지다.
여러가지 원인이 있는데 비교적 잦은 예를 들어보겠다.

1. 부모클래스가 들어있는 헤더파일을 include하지 않은 경우
해당 클래스가 있는 헤더파일을 포함시켜준다.

2. include는 하였지만 namespace이 달라 인식하지 못한 경우
부모클래스가 아래와 같이 어떤 namespace 내에 존재한다면..
namespace MySpace
{
    class Base
    {
    };
}

① namespace이름::class이름 .. 으로 처리한다.
class Derived : public MySpace::Base
{
};


② 또는 using namespace namespace이름; 을 써준다.
using namespace MySpace;

class Derived : public Base
{
};


3. 헤더파일을 include했으나 교차포함해서 인식하지 못한 경우
다음참조 : http://ekessy.tistory.com/20 (교차 포함 시 대량의 오류가 발생하는 경우)

2010. 6. 13. 09:56

교차 포함 시 대량의 오류가 발생하는 경우


error C2146: 구문 오류 : ';'이(가) ... 식별자 앞에 없습니다.
error C4430: 형식 지정자가 없습니다. int로 가정합니다. 참고: C++에서는 기본 int를 지원하지 않습니다.
error C4430: 형식 지정자가 없습니다. int로 가정합니다. 참고: C++에서는 기본 int를 지원하지 않습니다.

...

위와 같은 에러가 나는 이유는 클래스를 찾을 수 없는 경우에 발생합니다. 분명히 클래스가 있는데도 불구하고 이런 에러메시지가 발생하는 경우는.. 보통 교차포함을 한 경우에 이런 에러가 자주 발생합니다.
이게 무슨 얘기냐.. 다음을 보시죠..

b.h
#pragma once
#include "a.h"
class B
{
public:
 A a;
};

a.h
#pragma once
#include "b.h"
class A
{
public:
 B b;
};

양쪽의 클래스들은 서로를 필요로 하기 때문에 서로 #include을 해주었습니다.
언뜻 보기에는 문제가 없어보이죠. 실제로 자바에서는 에러가 나질 않습니다. 하지만 c++에서는 에러를 유발합니다. 무수히 많은 에러를 유발하죠.... 아주 많이....
 
하나하나 따라가보면 그 이유를 알 수 있습니다.
일단 b.cpp파일을 컴파일한다고 합시다. 그럼 b.cpp에는 최상단에 b.h가 include되어있을 겁니다. 그래서 컴파일러는 b.h를 포함시키죠. b.h를 가보니 상단에 a.h가 include되어있습니다. 그럼 그 헤더파일을 포함시키려 하겠죠. 그래서 a.h에 가보니.. 상단에 b.h가 include되어있습니다. 좀전에 b.cpp에서 b.h를 include했는데도 불구 한번더 include하려하지요.(아직 class B가 전부 읽혀진건 아닙니다.) 하지만 #pragma once구문 보이시죠? 이 구문때문에 헤더파일은 단 한번만 불려와지게 됩니다. 결국 a.h에 있는 #include "b.h"구문은 있으나 마나한 셈이 되죠. 그래서 class A의 B b부분에서 클래스 B는 이세상에 없는 클래스로 인식하고 형식이 없다는 에러메시지를 발생시키는 겁니다. 만약 class A에 포함된 에러 헤더파일 중 B를 필요로 하는 헤더파일이 있다면.. 에러메시지는 더욱더 많이 발생할 겁니다. 마찬가지로 a.cpp를 컴파일 하는경우 똑같은 에러가 나겠네요.

또한 서로 포함하지 않았는데도 불구, 이 에러메시지가 발생하는 경우가 있습니다.
그 경우는 서로는 포함하지 않았지만, 각 파일에 포함된 헤더파일들에 포함되어 간접적으로 서로를 포함한 경우가 해당됩니다.
b.h
#pragma once
#include "a.h"
class B
{
public:
 A a;
};

a.h
#pragma once
#include "c.h"
class A
{
public:
 B b;
};

c.h
#pragma once
#include "b.h"
class C
{
};


정말 간단하게 생각하면 #pragma once가 문제라고 생각하실 수 있습니다.
하지만 이걸 없애버리면 더 큰 문제가 발생합니다. class B가 두번 로드되는 경우죠. 심벌은 단 한개만 유효합니다. class B가 여러개 있을수는 없겠죠. 그렇다면, 단 한번만 불려와지고.. a.h에서도 인식할 수 있는 방법이 있느냐..? 물론 존재합니다. 여러가지 기법들이 존재하죠.



해결책

1. 가장 간단한 방법
헤더파일에서는 컴파일러에 단지 "암시"만 주고 포함시키지 않는 방법입니다.
b.h
#pragma once
class A; // class A가 존재한다고만 알려줍니다.
class B
{
public:
 A* a; // 포인터로 바꾸었습니다.
};

a.h
#pragma once
class B; // class B가 존재한다고만 알려줍니다.
class A
{
public:
 B* b;
};

※ 주의) 각각의 소스파일엔 포함시켜줘야합니다. 이렇게 하면 서로 include를 하지 않기 때문에 위와 같은 에러가 발생하지 않습니다. class A; 라고 하기만 하고 끝낸 부분이 눈에 띄는데, 이 부분은 실제로 클래스를 정의하진 않고 단지 앞으로 정의될 것이다, 나중에 링크과정때 찾아보라고 하는 일종의 암시입니다. 당장은 컴파일을 위해 때우는(?) 수단이라고 보시면 되겠죠. 그리고 A a에서 A* a로 포인터로 바뀐 부분을 알 수가 있는데, 일반 변수의 경우 크기를 알아야 하기 때문이죠. 따라서 일반 변수로 했다면 B를 컴파일 하는 도중 A의 크기를 구하려고 할 것이고, 에러를 유발하곤 합니다. 반면에 포인터 변수는 어떤 타입에도 상관없이 4바이트를 고수합니다.

한가지 주의하실 점은, 저렇게 하고 b.h에서 A의 멤버변수(함수)에 접근하는 인라인 함수같은 코드는 넣으시면 안됩니다. 컴파일 되는 당시로썬 A의 상세코드는 알길이 없기 때문이죠. 단 다음과 같은 경우는 가능합니다. 그 이유는 당장엔 A의 내용을 알필요가 없기 때문입니다. 소스파일에 넣으시기 바랍니다.
b.h
#pragma once
class A; // class A가 존재한다고만 알려줍니다.
class B
{
public:
 A* a; // 포인터로 바꾸었습니다.
 B() : a(NULL) {}
 ~B(){ if( a != NULL)delete a; } // 이 구문은 절대 안됩니다. 현재로썬 a의 크기를 알수 없기 때문입니다.
 A* GetAPtr() { return a; }
};




2. 좀더 나은 방법
b.h
#pragma once
#include "ABase.h"
#include "BBase.h"
class B : public BBase
{
public:
 B();
 virtual ~B();
 ABase* a;
};

a.h
#pragma once
#include "ABase.h"
#include "BBase.h"
class A : public ABase
{
public:
 A();
 virtual ~A();
 BBase* b;
};

b.cpp
#include "b.h"
#include "a.h"

B::B()
{
  a = new A();
}
B::~B()
{
  if( a!=NULL ) delete a;
}

BBase.h
class BBase
{
public:
  virtual ~BBase(){}
};

클래스를 상속으로 계층관계로 만들어버린 후, 부모클래스를 포함시키는 방법입니다. 정의는 부모클래스지만 당연히 할당은 자식클래스로 하게 되겠죠?^^; 이 방법은 실제로 자주 사용되고 있습니다. 유명한 패턴중 상태패턴에서도 이런 구조로 사용되죠. 그리고 컨테이너들도 저런 방식과 유사하게 설계되었을거라 생각합니다. 하지만, 무리하게 이 방식으로 할 필욘 없다고 생각합니다. 몇몇의 경우는 제외하고는 이 방식보단 처음방식이 더 나을수도 있겠습니다..^^



맺음말
실제로 이런 디자인은 말라고 합니다만.. 현재 라이브러리에서는 이런 디자인이 비일비재합니다. 가장 간단한 예로 MFC에서 부모와 차일드 윈도우 관계죠. 또는 게임 오브젝트 관리자와 게임 오브젝트들과 서로 포함시켜서 프로그래밍을 단순화 시키기도 합니다. 때로는 이런 디자인이 간편하고 가독성에도 일조한단 얘기죠.
2010. 4. 26. 04:02

증가연산자 혹은 증감연산자 사용시 주의사항

++, --사용시 주의사항입니다.

i++;
i--;
같이 자주쓰이는 연산자중 하나인데요.
간편하고 빨라서 아주 요긴하게 쓰이긴 하지만, 이 때문에 실수가 가끔 일어나기도 합니다.
이를테면....

i = 2;
int result = max( i--, 0 );

얼핏보면 제대로 나오는 것같지만 result가 0이 됩니다. 1을 예상했는데도 말이죠...
여기에서 max가 매크로로 되어있다면....?
이 경우는 max가 함수가 아닌 매크로로 되어있다면 가능한 일입니다.

WinDef.h에 정의되어있는 max매크로입니다.
#define max(a,b)            (((a) > (b)) ? (a) : (b))

즉, 이렇게 변경이 되겠죠.
int result = (((i--) > (0)) ? (i--) : (0))

따라서 0이 나오는 겁니다.


2010. 4. 23. 14:17

소스 파일이 모듈을 빌드했을 때와 다릅니다. 현재 위치에 사용할 수 있는 소스 코드가 없습니다.


소스 파일이 모듈을 빌드했을 때와 다릅니다.

상당히 짜증나는 부분입니다.
많은 분들의 얘기를 들어보면 그중 원인이 디버깅 심볼 데이터베이스를 구축하지 않아서 그럴수도 있다.. 라고 써있는데요 프로젝트 속성에 거의 들어가지 않는 시점에서 그 부분은 거의 신빙성이 없어 보입니다.
일단 첫번째 해결책은 솔루션 재빌드가 되겠습니다.
하지만 솔루션 재빌드를 해도 오류가 개선되지 않는다면 다음과 같은 시도를 해볼 수 있습니다.

http://cafe.naver.com/powermania/14
옵션>디버깅>일반>소스파일이 원래 버전과 정확하게 일치해야함 체크해제
하지만 저렇게 바꾸는 것은 왠지 찜찜한 기분이 들어서 저렇게 바꾸진 못하겠더군요.


따라서 곰곰이 생각해보던 중, 빌드된 시점과 소스파일의 인코딩 상태가 다르다는 점을 알 수 있었습니다. 또는 편집하고 있는 도중에 아주 간혹.. 오브젝트파일을 자신도 모르게 이전 버전으로 교체해버리는 경우도 있어요. 이건 저도 예전에 경험해봤던 일이었죠. 그래서 해당파일을 파일 > 고급 저장 옵션에서 유니코드로 바꿔준 후 다시 빌드하니까 그런 에러가 사라지더군요.

하지만 특정파일만 유니코드로 하고 나머지는 안시로 하라? 역시 찜찜한 기분이 들어서 못바꾸겠더군요.
그래서 해결방안은?
일단 해당파일의 소스를 복사한 후, 해당파일을 완전히 삭제한 후, 동명으로 새롭게 파일만들어서 소스코드를 붙여넣기 한 후 빌드했습니다.
결과는? 해결입니다. 제대로 되더군요.




현재 위치에 사용할 수 있는 소스 코드가 없습니다.

소스 파일이 모듈을 빌드했을 때와 다릅니다.의 경우는 에러는 나긴 하지만 나름 디버깅도 되고 쓸만했습니다.
하지만 어느순간부터 저 에러가 난다면..?
프로그래머는 소스 파일이.. 이 대화상자가 뜰 때 [아니오] 버튼을 눌러서 그렇습니다.
해당소스파일을 디버깅할 때 제외시키는 것이죠. 무의식적으로 아니오 누를 수 있는데.. 낭패보기 쉽습니다.
이 부분은 그냥 솔루션 > 속성 > 소스파일 디버그 부분의 다음 소스파일을 찾지않음 부분에서 해당파일을 삭제해주세요. 그럼 소스파일이.. 라는 대화상자를 다시 보실 수 있으실 겁니다.
그 후에는 위와 같은 해결책으로 해결하시면 되요!
2010. 4. 21. 23:49

메모리릭 검출 중에 황당한 오류를 접한 경우



대략 다음과 같은 에러가 발생하는 경우입니다..
warning C4229: 오래된 구문을 사용했습니다 : 데이터의 한정자가 무시됩니다.
error C2365: 'operator new' : 재정의: 이전 정의는 '함수'입니다.
error C2491: 'new' : dllimport 데이터을(를) 정의할 수 없습니다.
error C2078: 이니셜라이저가 너무 많습니다.
error C2440: '초기화 중' : 'int'에서 'void *'(으)로 변환할 수 없습니다.
...





아래와 같이 관련 헤더파일을(메모리릭.h) 헤더파일포함리스트의 제일 하단에 포함시켜주세요..

#include ...
#include ...
#include ...
#include "메모리릭.h"

int main()
{
   ...
   return 0;
}

2010. 4. 20. 03:14

ZeroMemory또는 memset시 유의사항


보통 데이터를 갖는 구조체들은 구조체에 값을 채우기전에 초기화작업을 많이 하고들 합니다. 보통 아래와 같은 구문을 주로 쓰는 편인데..
memset( &data, 0, sizeof( data ) );
또는
ZeroMemory( &data, sizeof( data ) );


하지만, 구조체에 STL이 포함되어있는 경우는 각별한 주의를 요합니다. 이를 테면..
struct Data
{
    string str;
    vector<int> nums;
};



실행중에는 별다른 이상이 없어보이지만, 디버깅을 하면, 메모리누수가 나는 것을 볼 수 있습니다.
많은 경우를 모두 테스트 해보지 않았지만, vector의 경우는 별다른 이상이 없었지만.. string의 경우는 메모리 누수가 발생합니다. str변수를 new를 통해 생성하지 않았음에도 말이죠. 사실 내부적으로도 동적 할당되구요. 차례대로 따라가다보시면 더 확실히 알 수 있습니다.

어쨌든 별생각없이 ZeroMemory한 결과였고, 실행중에 정상적으로 나오기 때문에 별다른 걸 못느꼈지만, 디버깅을 해보니 누수되었다고 나오더군요.



결론은 STL변수의 저장공간을 절대 0으로 초기화하지 말아야 합니다. 당연한 얘기일수도 있으나, 의외로 실수가 잦으니 항상 숙지하시기 바랍니다.