정적 클래스 멤버에 대한 정의되지 않은 참조


201

다음 코드가 컴파일되지 않는 이유를 누구나 설명 할 수 있습니까? 적어도 g ++에서 4.2.4.

그리고 더 흥미로운 것은 MEMBER를 int로 캐스팅 할 때 왜 컴파일됩니까?

#include <vector>

class Foo {  
public:  
    static const int MEMBER = 1;  
};

int main(){  
    vector<int> v;  
    v.push_back( Foo::MEMBER );       // undefined reference to `Foo::MEMBER'
    v.push_back( (int) Foo::MEMBER ); // OK  
    return 0;
}

<pre> <code> </ code> </ pre> 대신 4 개의 공백으로 코드를 들여 쓰도록 질문을 편집했습니다. 이것은 꺾쇠 괄호가 HTML로 해석되지 않음을 의미합니다.
Steve Jessop

stackoverflow.com/questions/16284629/… 이 질문을 참조 할 수 있습니다.
Tooba Iqbal

답변:


199

실제로 클래스 정의 다음에 어딘가에 정적 멤버를 정의해야합니다. 이 시도:

class Foo { /* ... */ };

const int Foo::MEMBER;

int main() { /* ... */ }

정의되지 않은 참조를 제거해야합니다.


3
좋은 점, 인라인 정적 const 정수 초기화는 주소를 사용할 수없는 범위가 지정된 정수 상수를 만들고 벡터는 참조 매개 변수를 사용합니다.
Evan Teran

10
이 답변은 질문의 첫 부분 만 다룹니다. 두 번째 부분은 훨씬 더 흥미 롭습니다. 왜 NOP 캐스트를 추가하면 외부 선언없이 작동합니까?
nobar

32
클래스 정의가 헤더 파일에 있으면 정적 변수의 할당이 헤더가 아닌 구현 파일에 있어야한다는 것을 알아내는 데 약간의 시간을 보냈습니다.
shanet

1
@ shanet : 아주 좋은 지적-내 대답에 언급해야합니다!
Drew Hall

그러나 const로 선언하면 해당 변수의 값을 변경할 수 없습니까?
Namratha

78

새로운 C ++ 기능의 흥미로운 충돌과 수행하려는 작업으로 인해 문제가 발생합니다. 먼저 push_back서명을 살펴 보겠습니다 .

void push_back(const T&)

유형의 객체에 대한 참조가 필요합니다 T. 이전 초기화 시스템에서 이러한 멤버가 존재합니다. 예를 들어 다음 코드는 정상적으로 컴파일됩니다.

#include <vector>

class Foo {
public:
    static const int MEMBER;
};

const int Foo::MEMBER = 1; 

int main(){
    std::vector<int> v;
    v.push_back( Foo::MEMBER );       // undefined reference to `Foo::MEMBER'
    v.push_back( (int) Foo::MEMBER ); // OK  
    return 0;
}

그 값이 저장된 실제 객체가 있기 때문입니다. 그러나 위와 같이 정적 const 멤버를 지정하는 새로운 방법으로 전환하면 Foo::MEMBER더 이상 객체가 아닙니다. 다음과 비슷한 상수입니다.

#define MEMBER 1

그러나 전 처리기 매크로의 어려움없이 (그리고 유형 안전). 즉, 참조를 기대하는 벡터는 벡터를 얻을 수 없습니다.


2
덕분에 도움이되었다고 ...을받을 수있는 stackoverflow.com/questions/1995113/strangest-language-feature 가 ... 있지 않은 경우
앙드레 HOLZNER

1
또한 MSVC는 불만없이 비 캐스트 버전을 허용합니다.
23시

4
-1 : 이것은 사실이 아닙니다. 정적 멤버가 어딘가 에서 사용 되면 인라인으로 초기화 된 정적 멤버를 정의해야합니다 . 컴파일러 최적화는 링커 오류를 제거해도 변경되지 않습니다. 이 경우 (int)상수에 대한 완벽한 가시성으로 변환 단위에서 lvalue-to-rvalue 변환 ( 캐스트로 인한 변환)이 발생하며 Foo::MEMBER더 이상 odr-used 가 아닙니다 . 이것은 참조가 전달되고 다른 곳에서 평가되는 첫 번째 함수 호출과 대조적입니다.
궤도에서 가벼움 경주

무엇에 대해 void push_back( const T& value );? const&의 rvalue와 바인딩 할 수 있습니다.
코스타스

59

정의가 어떻게 든 필요한 경우 C ++ 표준에는 정적 const 멤버에 대한 정의가 필요합니다.

주소가 사용되는 경우와 같이 정의가 필요합니다. push_backconst 참조로 매개 변수를 취하므로 컴파일러는 엄격하게 멤버의 주소가 필요하며 네임 스페이스에서 정의해야합니다.

명시 적으로 상수를 캐스트하면 임시를 작성하는 것이 표준의 특수 규칙에 따라 참조에 바인딩 된 임시입니다.

이것은 정말 흥미로운 경우이며, 실제로 std가 상수 멤버와 동일한 동작을 갖도록 변경되도록 문제를 제기 할 가치가 있다고 생각합니다!

이상한 방식으로 이것은 단항 '+'연산자를 합법적으로 사용하는 것으로 볼 수 있습니다. 기본적으로의 결과 unary +는 rvalue이므로 rvalue를 const 참조에 바인딩하는 규칙이 적용되며 정적 const 멤버의 주소를 사용하지 않습니다.

v.push_back( +Foo::MEMBER );

3
+1. 예, T 형의 객체 x에 대해 "(T) x"표현을 사용하여 const 참조를 바인딩 할 수 있지만 일반 "x"는 할 수 없습니다. 나는 "단항 +"에 대한 당신의 관찰을 사랑합니다! 누가 불쌍한 "단항 +"실제로 :) ... 용도 있다고 생각이나 했 겠어요
j_random_hacker을

3
일반적인 경우를 생각하면 ... C ++에는 (1) 정의 된 경우에만 lvalue로 사용할 수 있지만 (2) rvalue로 변환 할 수없는 속성이있는 다른 유형의 객체가 있습니까? 한정된?
j_random_hacker

좋은 질문이며 적어도 현재로서는 다른 예를 생각할 수 없습니다. 위원회가 대부분 기존 구문을 재사용했기 때문에 이것은 아마도 여기에만있을 것입니다.
Richard Corden

@RichardCorden : 단항 +는 어떻게 그것을 해결 했습니까?
Blood-HaZaRd

1
@ Blood-HaZaRd : rvalue가 참조하기 전에 유일한 과부하 push_back는 a const &입니다. 멤버를 직접 사용하면 멤버가 참조에 바인딩되어 주소가 필요했습니다. 그러나를 추가 +하면 멤버 값으로 임시가 작성됩니다. 그런 다음 참조는 구성원에게 주소를 요구하지 않고 임시에 바인딩합니다.
Richard Corden

10

Aaa.h

class Aaa {

protected:

    static Aaa *defaultAaa;

};

Aaa.cpp

// You must define an actual variable in your program for the static members of the classes

static Aaa *Aaa::defaultAaa;

1

캐스트가 작동하는 이유는 모릅니다. 그러나 Foo :: MEMBER는 Foo가 처음로드 될 때까지 할당되지 않으며, 절대로드하지 않으므로 할당되지 않습니다. 어딘가에 Foo에 대한 참조가 있다면 아마도 효과가있을 것입니다.


나는 당신이 당신 자신의 질문에 대답하고 있다고 생각합니다 : 캐스트는 (임시의) 참조를 생성하기 때문에 작동합니다.
Jaap Versteegh

1

C ++ 11을 사용하면 위와 같은 기본 유형이 가능합니다.

class Foo {
public:  
  static constexpr int MEMBER = 1;  
};

constexpr부분은 정적 변수 와 달리 정적 표현식 을 작성하며 매우 간단한 인라인 메소드 정의처럼 작동합니다. 그러나이 접근 방식은 템플릿 클래스 내부에 C- 문자열 constexprs를 사용하여 약간 흔들렸다.


"static const int MEMBER = 1;"이기 때문에 이것은 나에게 필수적인 것으로 판명되었습니다. 스위치에서 MEMBER를 사용하는 데 필요하지만 벡터에서 사용하려면 외부 선언이 필요하며 동시에 둘 다 가질 수는 없습니다. 하지만 여기에주는 표현은 하지 내 컴파일러와 함께 적어도, 모두 일을.
벤 파머

0

두 번째 질문과 관련하여 push_ref는 매개 변수로 참조를 사용하므로 클래스 / 구조체의 정적 const 멤버에 대한 참조를 가질 수 없습니다. static_cast를 호출하면 임시 변수가 작성됩니다. 그리고이 객체에 대한 참조를 전달할 수 있으며 모든 것이 잘 작동합니다.

또는 적어도 이것을 해결 한 동료가 그렇게 말했습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.