'printf 에러'에 해당되는 글 1건

  1. 2010.06.10 암시적 변환 함수의 함정
말은 거창하지만 실상은 단순하다.
 
많은 선배 프로그래머들이 충고하고 MEC++ 책(※ More Effective C++ 스캇 마이어스 저, 항목 5)에서 강조한 부분이 있기도 하지만 후배 프로그래머들에게 잘 와닿지도 않는 모호한 경고를 앵무새처럼 늘어놓고 싶은 생각은 없다.
 
나는 대원칙만 제대로 세워두었다면 암시적 변환의 실보다는 득을 더 많이 얻을 수 있다고 생각하는 편이다.


다음 코드를 보자.

struct SB_TYPE_RELS    // 인간관계를 나타내는 타입
{
private:
unsigned char value;
public:
operator char*()
{
static char* s_sz[] =
{
"일반",
"아군",
"적군",
};

return s_sz[value];
}
};

위의 타입은 실제로 어떤 ENUM 타입의 값을 갖고 있지만 그것을 텍스트로 출력할 필요가 있기 때문에

다음과 같이 사용된다.

SB_TYPE_RELS Rels = SB_RELS_일반;
cout << Rels << endl;

이 경우 Rels는 암시적으로 char* 로 변환이 가능하기 때문에 operator <<  함수를 따로 정의하지 않아도 컴파일러가 처리해주게 된다.


그런데 주의할 점이 있다.

다음과 같은 가변 인자에서
printf("관계 : %s", Rels);

우리 생각엔 Rels가 "일반"으로 바뀌어서 %s 자리에 쏙 들어가야할 것 같은데 실제 컴파일을 하면 에러가 난다.

이유는 printf 가 내부에서 만들어질 때 %s를 만나면 인자에서 주소를 대입하게 되는데 그 모습이 우리 상상과는 많이 다르기 때문이다.
 
요점만 얘기하자면 컴파일러는 Rels가 char* 로 변환되어야한다는 사실을 모른다. 그래서 끔찍한 메모리 포인터 에러를 뱉는다.



두번째 주의점은 암시적 변환의 경우 미리 정의만 되어 있다면 얼마든지 몇 번에 걸쳐 변환을 해주지만,

변환 생성자의 경우는 외부 타입 -> 현재 클래스의 타입으로 만들어내는 방식이기 때문에

컴파일러가 두 번 이상 암시적으로 변환을 해주진 않는다는 것이다.


꿍얼꿍얼... 뭔 소린지 모르겠으니 역시 예제 코드를 보자


class CString
{
public:
CString(const std::string& sz);          // STL 의 string 타입을 통해 CString 인스턴스를 생성한다

bool operator == (const CString& s);  // 두 문자열이 서로 같은지 비교해본다
};

void main()
{
    CString  s("Hello World");      // 문제 없다. "" 문자열은 string 으로 암시적으로 한차례 변환되어 들어간다
    if( s == "Hello" )                   // 에러! "Hello" 를 CString 으로 변환할 수가 없다
}


오우, 안되는게 당연하지! 라면서 명확해 보인다고? 당신은 앞으로 크게 될 가능성이 있다.

그럼 다음과 같은 경우는 어떨까?


class CString
{
public:
CString(const std::string& sz);          // STL 의 string 타입을 통해 CString 인스턴스를 생성한다
CString(const CString& o);               // 익숙한 복사 생성자가 추가되었다
};

void main()
{
CString k = "Hello";   // 초기화를 했는데
k = "World";             // 바꾸고 싶다. 근데 에러가 났어 읭!?
}


"헐, 이거 뭐야." 라는 반응이 나오는가 아니면 "당연히 안되지!" 라는 반응이 나오는가? "이게 뭥미" 라는 반응이라면, 당신 조금 곤란하다.

조금만 생각해보면 "World" 는 CString 타입으로 암시적 변환이 가능하므로("World" => string 타입 => CString)

CString("World") 로 변환한 다음 복사 생성자를 호출하면 될 것 같다.

하지만 컴파일러는 저런 연산 과정을 할 수 없다. 우리 눈에만 명확해보이는 것이다.

그 기저에는 복사 생성자가 변환 생성자로 취급되지 않는다는 원칙이 깔려있다는 사실을 명심하자.



6월 14일자로 이 뒷부분의 내용이 생각나서 다시 이어 써본다.

그렇다면 복사 생성자일 때만 저것이 에러가 나는가? 그렇지 않다. 일반적으로 2번 변환을 하게 되면

컴파일러는 변환 과정 중에 n X m 크기의 테이블을 만들어서 변환을 해야할텐데, 이것의 성능이 도저히 측정이 되지 않기 때문에

뺄 수밖에 없는 것이다.


예를 들어 위의 경우 "World" 가 string 이 되고 그것이 다시 CString(const string&) 이 된다는 사실을 찾으려면

1차 변환 대조표와 2차 변환 대조표를 늘어놓고 일일이 매칭을 시켜봐야하는데 이게 몇 개가 나올지 예측이 안되므로

2번 이상의 암시적 변환은 허용하지 않고 있다.
(그러고도 못 찾을 수 있으니 컴파일러 입장에서는 그냥 에러를 뱉는게 속편하지 않은가?)


Posted by OOJJRS
,