스토리텔링 개발자

[Effective STL] 49. 컴파일러 에러 해석하기 본문

개발/Effective STL

[Effective STL] 49. 컴파일러 에러 해석하기

김디트 2025. 1. 31. 10:23
728x90

항목 49. STL에 관련된 컴파일러 진단 메시지를 해석하는 능력을 가지자

 

 

 

컴파일러 에러 예시 1
vector<int> v(10); // 크기 10의 벡터를 하나 만든다. 성공.

string s(10); // 크기 10인 string 객체를 만든다? 실패
  • 비주얼 c++에서 아래와 같은 에러가 발생한다.
example.cpp(20): error C2664:'__thiscall std::basic_string<char, struct
std::char_traits<char>,class std::allocator<char>
>::std::basic_string<char, struct std::char_traits<char>,class
std::allocator<char> >(const class std::allocator<char> &)': cannot convert
parameter 1 from 'const int' to 'const class std::allocator<char> &'
 
Reason: cannot convert from 'const int' to 'const class std::allocator<char>'
 
No constructor could take the source type, or constructor overload
resolution was ambiguous
  • string은 클래스가 아니라, 아래 형태로 만든 typedef 타입이다.
basic_string<char, char_traits<char>, allocator<char> >

 

  • C++에서 쓰이는 string처럼 동작하는 모든 객체는 basic_string 템플릿을 인스턴스화한 것이다.
    • 그렇기에 에러 메시지에 basic_string이 언급되는 것이다.
    • 즉, 위 에러 메시지에서 basic_string 부분은 string으로 치환 가능하다.
example.cpp(20): error C2664:'__thiscall string::string(const class
std::allocator<char> &)': cannot convert parameter 1 from 'const int' to
'const class std::allocator<char> &'
  • 할당자를 언급하는 이유?
    • 모든 표준 컨테이너엔 할당자를 받아들이는 생성자가 준비되어 있다.
    • 컴파일러는 몇 가지 이유 때문에 매개 변수를 하나 넣은 생성자를 무조건 할당자를 받아들이는 할당자로 간주해 버린다.
    • 즉, 컴파일러가 착각했고 이 에러 메시지는 틀렸다.
  • 할당자를 받는 생성자는 웬만하면 쓰지 말자.
    • 이 생성자는 해당 컨테이너와 동일한 타입인데, 동등하지 않은(inequivalent) 할당자를 가진 상태로 빠뜨려 버린다.(항목 11 참조)

 

 

 

컴파일러 에러 예시 2
class NiftyEmailProgram
{
private:
    using NicknameMap = map<string, string>;
    NicknameMap nicknames; // 닉네임 - email 주소
public:
...
    void showEmailAddress(const string& nickname) const;
};

// 구현에서 에러가 발생한다...
void NiftyEmailProgram::showEmailAddress(const string& nickname) const
{
    ...
    NicknameMap::iterator i = nicknames.find(nickname);
    if(i != nicknames.end()) ...
    ...
}
example.cpp(17): error C2440: 'initializing': cannot convert from 'class std::_Tree<class
std::basic_string<char, struct std::char_traits<char>,class std::allocator<char> >,struct
std::pair<class std::basic_string<char, struct std::char_traits<char>,class
std::allocator<char> > const .class std::basic_string<char, struct
std::char_traits<char>,class std::allocator<char> > >,struct std::map<class
std::basic_string<char, struct std::char_traits<char>,class std::allocator<char> >.class
std::basic_string<char, struct std::char_traits<char>,class std::allocator<char> >,struct
std::less<classstd::basic_string<char,structstd::char_traits<char>, class
std::allocator<char> > >,class std::allocator<class std::basic_string<char, struct,
std::char_traits<char>,class std::allocator<char> > > >::_Kfn, struct std::less<class
std::basic_string<char, struct std::char_traits<char>,class std::allocator<char> > >,class
std::allocator<class std::basic_string<char, struct, std::char_traits<char>,class
std::allocator<char> > > >::const_iterator' to 'class std::_Tree<class
std::basic_string<char, struct std::char_traits<char>,class std::allocator<char> >,struct
std::pair<class std::basic_string<char, struct std::char_traits<char>,class
std::allocator<char> > const .class std::basic_string<char, struct
std::char_traits<char>,class std::allocator<char> > >,struct std::map<class
std::basic_string<char, struct std::char_traits<char>,class std::allocator<char> >,class
std::basic_string<char, struct std::char_traits<char>,class std::allocator<char> >,struct
std::less<classstd::basic_string<char,structstd::char_traits<char> .class
std::allocator<char> > >,class std::allocator<class std::basic_string<char,struct
std::char_traits<char>,class std::allocator<char> > > >::_Kfn, struct std::less<class
std::basic_string<char, struct std::char_traits<char>,class std::allocator<char> > >,class
std::allocator<class std::basic_string<char, struct std::char_traits<char>,class
std::allocator<char> > > >::iterator'
No constructor could take the source type, or constructor overload resolution was
ambiguous
  • 일단 위에서 배운대로 basic_string을 string으로 치환하여 간소화시킨다.
example.cpp(17): error C2440: 'initializing': cannot convert from 'class
std::_Tree<class string, struct std::pair<class string const ,class string
>,struct std::map<class string, class string, struct std::less<class string
>,class std::allocator<class string > >::_Kfn, struct std::less<class string
>,class std::allocator<class string > >::const_iterator' to 'class
std::_Tree<class string, struct std::pair<class string const .class string
>,struct std::map<class string, class string, struct std::less<class string
>,class std::allocator<class string > >::_Kfn,struct std::less<class string
>,class std::allocator<class string > >::iterator'
 
No constructor could take the source type, or constructor overload
resolution was ambiguous
  • std::_Tree
    • 밑줄(_)과 대문자는 라이브러리 제작자들에게 예약된 것이다.
    • 즉, STL의 컴포넌트를 구현하는 데 사용되는 내부 템플릿이다.
  • std::map<class string, class string, struct std::less<class string >, class std::allocator<class string> >
    • 우리가 사용한 맵의 정확한 타입이다.
    • 이를 우리가 선언한 이름(NickNameMap)으로 바꾸면 또 간소화시킬 수 있다.
example.cpp(17): error C2440: 'initializing': cannot convert from 'class
std::_Tree<class string, struct std::pair<class string const, class string
>,struct NicknameMap::_Kfn, struct std::less<class string >,class
std::allocator<class string > >::const_iterator' to 'class std::_Tree<class
string, struct std::pair<class string const ,class string >,struct
NicknameMap::_Kfn, struct std::less<class string >,class std::allocator<class
string > >::iterator'
 
No constructor could take the source type, or constructor overload resolution was ambiguous
  • 사실 _Tree는 내부 템플릿이므로 에러를 이해하기 위해 굳이 깊게 이해할 필요가 없다.
    • SOMETHING으로 치환해 버리자.
example.cpp(17): error C2440: 'initializing': cannot convert from 'class
std::_Tree<SOMETHING>::const_iterator' to 'class
std::_Tree<SOMETHING>::iterator'
 
No constructor could take the source type, or constructor overload
resolution was ambiguous
  • 이해하기 쉬운 코드가 되었다.
    • const_iterator를 iterator로 변환하려고 해서 발생한 에러임이 일목요연하다.
    • showEmailAddress가 const 멤버 함수이기에 NicknameMap::iterator i = nicknames.find(nickname);의 반복자가 const_iterator가 된 것이었다.

 

 

 

STL 컴파일러 에러 이해하기 팁
  • vector와 string의 경우, 반복자는 포인터와 똑같으므로, iterator를 가지고 실수를 했다면 컴파일러 진단 메시지는 포인터 타입을 언급할 가능성이 매우 높다.
    • 예를 들어 소스 코드에 vector<double>::iterator가 들어있다면 컴파일러 메시지는 십중팔구 double* 라고 한다.
  • back_insert_iterator, front_insert_iterator, insert_iterator 등을 운운하는 메시지는 거의 항상 back_inserter나 front_inserter, inserter를 호출할 때 실수를 저질렀다는 뜻이다.(항목 30 참조)
  • binder1st나 binder2nd 등이 언급된다면 bind1st, bind2nd를 잘못 호출했다고 생각하면 된다.
  • 출력 반복자(ostream_iterator, ostreambuf_iterator(항목 29 참조), back_inserter, front_inserter, inserter에서 반환된 반복자들)에 대해 실수했을 때는 대입 연산자를 메시지에서 발견할 수 있다.
    • 출력 반복자는 대입 연산자(operator=)의 내부에서 출력, 삽입 동작을 하기 때문이다.
  • STL 알고리즘의 내부가 잘못되었다는 에러 메시지라면, 그 알고리즘과 함께 사용한 타입에 문제가 있다는 뜻이다.
    • 예를 들어 잘못된 반복자를 넘겼다던가 하는 이유일 것이다.
  • vector나 string, for_each 와 같이 자주 쓰이는 STL 컴포넌트를 사용하고 있는데 컴파일러 쪽에서 이해를 못하겠다는 에러를 발생시킨다면, #include를 빼먹은 것이다.
    • 지금은 되도 다른 플랫폼으로 이식할 때 문제가 발생할 수 있다.(항목 48 참조)
728x90
Comments