[c++] 정적 const int에 대한 정의되지 않은 참조

오늘 흥미로운 문제가 발생했습니다. 이 간단한 예를 고려하십시오.

template <typename T>
void foo(const T & a) { /* code */ }

// This would also fail
// void foo(const int & a) { /* code */ }

class Bar
{
public:
   static const int kConst = 1;
   void func()
   {
      foo(kConst);           // This is the important line
   }
};

int main()
{
   Bar b;
   b.func();
}

컴파일 할 때 오류가 발생합니다.

Undefined reference to 'Bar::kConst'

자, 나는 이것이 static const int어디에도 정의되지 않았기 때문이라고 확신합니다. 이것은 내 이해에 따르면 컴파일러가 컴파일 타임에 대체를 할 수 있어야하고 정의가 필요하지 않기 때문입니다. 그러나 함수는 const int &매개 변수를 취하기 때문에 대체를하지 않고 대신 참조를 선호하는 것 같습니다. 다음과 같이 변경하여이 문제를 해결할 수 있습니다.

foo(static_cast<int>(kConst));

나는 이것이 컴파일러가 임시 int를 만들고 그에 대한 참조를 전달하도록 강요하고 있다고 믿습니다. 컴파일 타임에 성공적으로 수행 할 수 있습니다.

이것이 의도적 인 것인지 궁금하거나 gcc에서이 사건을 처리 할 수있을 것으로 너무 많이 기대하고 있습니까? 아니면 내가 어떤 이유로해서는 안되는 일인가?



답변

의도적입니다. 9.4.2 / 4는 다음과 같이 말합니다.

정적 데이터 멤버가 const 정수 또는 const 열거 형인 경우 클래스 정의의 선언은 정수 상수 식 (5.19)이 될 상수 이니셜 라이저를 지정할 수 있습니다.이 경우 멤버는 정수 상수 식에 나타날 수 있습니다. 멤버는 프로그램에서 사용되는 경우 네임 스페이스 범위에서 정의됩니다.

const 참조로 정적 데이터 멤버를 전달할 때 3.2 / 2를 “사용”합니다.

정수 상수 표현식이 필요한 곳에 표시되거나 (5.11 참조), sizeof 연산자 (5.3.3)의 피연산자이거나, typeid 연산자의 피연산자이고 표현식이 다음의 lvalue를 지정하지 않는 경우 표현식은 잠재적으로 평가됩니다. 다형성 클래스 유형 (5.2.8). 개체 또는 오버로드되지 않은 함수는 이름이 잠재적으로 평가 된 식에 나타나는 경우 사용됩니다.

따라서 실제로 값으로 전달하거나 static_cast. 단지 GCC가 한 경우에 당신을 낚아 채게했지만 다른 경우에는 그렇지 않습니다.

[편집 : gcc는 C ++ 0x 초안의 규칙을 적용합니다. “이름이 잠재적으로 평가 된 표현식으로 나타나는 변수 또는 오버로드되지 않은 함수는 상수에 표시하기위한 요구 사항을 충족하는 객체가 아닌 한 odr 사용됩니다. 표현식 (5.19)과 lvalue에서 rvalue 로의 변환 (4.1)이 즉시 적용됩니다. ” 정적 캐스트는 lvalue-rvalue 변환을 즉시 수행하므로 C ++ 0x에서는 “사용”되지 않습니다.]

const 참조의 실제 문제 foo는 인수의 주소를 가져 와서이를 글로벌에 저장된 다른 호출의 인수 주소와 비교할 수있는 권한 내에 있다는 것 입니다. 정적 데이터 멤버는 고유 한 개체이므로 foo(kConst)두 개의 다른 TU에서 호출하는 경우 전달되는 개체의 주소가 각 경우에 동일해야합니다. AFAIK GCC는 객체가 하나의 TU에 정의되어 있지 않으면이를 정렬 할 수 없습니다.

foo좋습니다. 이 경우 는 템플릿이므로 정의는 모든 TU에서 볼 수 있습니다. 따라서 아마도 컴파일러는 주소로 무엇이든 할 수있는 위험을 이론적으로 배제 할 수 있습니다. 그러나 일반적으로 존재하지 않는 객체에 대한 주소 또는 참조를 사용해서는 안됩니다 😉


답변

클래스 선언 내부에 이니셜 라이저를 사용하여 정적 const 변수를 작성하는 경우 마치 작성한 것처럼

class Bar
{
      enum { kConst = 1 };
}

GCC는 동일한 방식으로 처리합니다. 즉, 주소가 없습니다.

올바른 코드는

class Bar
{
      static const int kConst;
}
const int Bar::kConst = 1;


답변

이것은 정말 유효한 경우입니다. 특히 때문에 같은 STL에서 기능 할 수있는 표준 : 카운트 얻어 CONST T 및 세 번째 인수로한다.

링커가 이러한 기본 코드에 문제가있는 이유를 이해하려고 많은 시간을 보냈습니다.

오류 메시지

‘Bar :: kConst’에 대한 정의되지 않은 참조

링커가 기호를 찾을 수 없음을 알려줍니다.

$nm -C main.o
0000000000000000 T main
0000000000000000 W void foo<int>(int const&)
0000000000000000 W Bar::func()
0000000000000000 U Bar::kConst

Bar :: kConst가 정의되지 않은 것을 ‘U’에서 볼 수 있습니다. 따라서 링커가 작업을 수행하려고 할 때 기호를 찾아야합니다. 그러나 kConst 만 선언 하고 정의하지 않습니다.

C ++의 솔루션은 다음과 같이 정의하는 것입니다.

template <typename T>
void foo(const T & a) { /* code */ }

class Bar
{
public:
   static const int kConst = 1;
   void func()
   {
      foo(kConst);           // This is the important line
   }
};

const int Bar::kConst;       // Definition <--FIX

int main()
{
   Bar b;
   b.func();
}

그런 다음 컴파일러가 생성 된 개체 파일에 정의를 넣는 것을 볼 수 있습니다.

$nm -C main.o
0000000000000000 T main
0000000000000000 W void foo<int>(int const&)
0000000000000000 W Bar::func()
0000000000000000 R Bar::kConst

이제 데이터 섹션에 정의되어 있다는 ‘R’을 볼 수 있습니다.


답변

g ++ 버전 4.3.4는이 코드를 허용합니다 ( 이 링크 참조 ). 그러나 g ++ 버전 4.4.0은이를 거부합니다.


답변

이 C ++의 인공물은 Bar::kConst 은 참조 할 리터럴 값이 대신 사용 .

이것은 실제로 참조 점을 만들 변수가 없음을 의미합니다.

다음을 수행해야 할 수 있습니다.

void func()
{
  int k = kConst;
  foo(k);
}


답변

constexpr 멤버 함수로 바꿀 수도 있습니다.

class Bar
{
  static constexpr int kConst() { return 1; };
};


답변

간단한 트릭 : 함수를 전달 +하기 전에 사용 kConst하십시오. 이렇게하면 상수가 참조되는 것을 방지 할 수 있으며, 이런 식으로 코드는 상수 개체에 대한 링커 요청을 생성하지 않지만 대신 컴파일러 시간 상수 값을 사용합니다.