클래스 정의에서 정적 const 정수 멤버 정의


109

내 이해는 C ++에서는 정수 유형 인 한 클래스 내에서 정적 const 멤버를 정의 할 수 있다는 것입니다.

그러면 다음 코드에서 링커 오류가 발생하는 이유는 무엇입니까?

#include <algorithm>
#include <iostream>

class test
{
public:
    static const int N = 10;
};

int main()
{
    std::cout << test::N << "\n";
    std::min(9, test::N);
}

내가 얻는 오류는 다음과 같습니다.

test.cpp:(.text+0x130): undefined reference to `test::N'
collect2: ld returned 1 exit status

흥미롭게도 std :: min에 대한 호출을 주석 처리하면 코드가 잘 컴파일되고 연결됩니다 (이전 행에서도 test :: N이 참조 되었음에도 불구하고).

무슨 일이 일어나고 있는지 아십니까?

내 컴파일러는 Linux에서 gcc 4.4입니다.


3
Visual Studio 2010에서 잘 작동합니다.
Puppy

4
이 정확한 오류에 설명 gcc.gnu.org/wiki/...
조나단 Wakely을

의 특별한 경우 char에는 대신 constexpr static const char &N = "n"[0];. 를 참고 &. 리터럴 문자열이 자동으로 정의되기 때문에 이것이 작동한다고 생각합니다. 나는 이것에 대해 다소 걱정됩니다. 문자열이 여러 다른 주소에있을 것이므로 다른 번역 단위 사이의 헤더 파일에서 이상하게 작동 할 수 있습니다.
아론 McDaid

1
이 질문은 "상수에 #defines를 사용하지 마십시오"에 대한 C ++ 답변이 여전히 얼마나 열악한 지에 대한 명백한 것입니다.
요하네스 Overmann

1
@JohannesOvermann 이와 관련하여 C ++ 17 이후 전역 변수에 대한 인라인 사용에 대해 언급하고 싶습니다. inline const int N = 10내 지식에는 여전히 링커가 정의한 저장소가 있습니다. 이 경우 키워드 인라인을 사용 하여 클래스 정의 테스트 내에서 정적 변수 정의 를 제공 할 수도 있습니다 .
Wormer

답변:


72

내 이해는 C ++에서는 정수 유형 인 한 클래스 내에서 정적 const 멤버를 정의 할 수 있다는 것입니다.

당신은 일종의 맞습니다. 클래스 선언에서 정적 const 정수를 초기화 할 수 있지만 이는 정의가 아닙니다.

http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=/com.ibm.xlcpp8a.doc/language/ref/cplr038.htm

흥미롭게도 std :: min에 대한 호출을 주석 처리하면 코드가 잘 컴파일되고 연결됩니다 (이전 행에서도 test :: N이 참조 되었음에도 불구하고).

무슨 일이 일어나고 있는지 아십니까?

std :: min은 매개 변수를 const 참조로 사용합니다. 값을 기준으로했다면이 문제는 없지만 참조가 필요하기 때문에 정의도 필요합니다.

다음은 장 / 절입니다.

9.4.2 / 4 - 경우 static데이터 부재이다 const정수 또는 const열거 형 클래스 정의의 선언이 지정 일정한 이니셜 적분 상수 식 (5.19)한다. 이 경우 멤버는 정수 상수 식에 나타날 수 있습니다. 멤버는 프로그램에서 사용되는 경우 네임 스페이스 범위에서 정의되어야하며 네임 스페이스 범위 정의에는 이니셜 라이저가 포함되지 않아야합니다 .

가능한 해결 방법은 Chu의 답변을 참조하십시오.


흥미 롭군요. 이 경우 선언 시점에 값을 제공하는 것과 정의 시점에 값을 제공하는 것의 차이점은 무엇입니까? 어느 것이 권장됩니까?
HighCommander4 2010-06-11

글쎄, 나는 당신이 실제로 변수를 "사용"하지 않는 한 정의 없이도 벗어날 수 있다고 믿습니다. 상수 표현식의 일부로 만 사용하면 변수가 사용되지 않습니다. 그렇지 않으면 헤더의 값을 볼 수 있다는 것 외에 큰 차이가없는 것 같습니다. 원하는 값일 수도 있고 아닐 수도 있습니다.
Edward Strange

2
간결한 대답은 static const x = 1입니다. rvalue이지만 lvalue가 아닙니다. 이 값은 컴파일 타임에 상수로 사용할 수 있습니다 (배열로 차원을 지정할 수 있음) static const y; [초기화 없음]은 cpp 파일에 정의되어야하며 rvalue 또는 lvalue로 사용될 수 있습니다.
Dale Wilson

2
그들이 이것을 확장 / 개선 할 수 있다면 좋을 것입니다. 초기화되었지만 정의되지 않은 객체는 리터럴과 동일하게 취급되어야한다고 생각합니다. 예를 들어 리터럴 5const int&. 그렇다면 OP를 test::N해당 리터럴로 취급하지 않는 이유 는 무엇입니까?
아론 McDaid

흥미로운 설명, 감사합니다! 이것은 C ++에서 static const int가 정수 #defines를 대체하지 않음을 의미합니다. enum은 항상 부호있는 int이므로 개별 상수에 대해 enum 클래스를 사용해야합니다. 상수로 상수 선언을 퇴화시키고 문제없이 컴파일되는 방식으로 리터럴 상수로 값을 아는 것은 나에게 매우 분명 할 것입니다. C ++은 ... 갈 길이 멀다
요하네스 Overmann에게

51

그의 C ++ FAQ에있는 Bjarne Stroustrup의 예 는 당신이 정확하다고 제안하며 주소를 사용하는 경우에만 정의가 필요합니다.

class AE {
    // ...
public:
    static const int c6 = 7;
    static const int c7 = 31;
};

const int AE::c7;   // definition

int f()
{
    const int* p1 = &AE::c6;    // error: c6 not an lvalue
    const int* p2 = &AE::c7;    // ok
    // ...
}

그는 "정적 멤버의 주소는 클래스 밖의 정의가있는 경우에만 사용할 수 있습니다." 라고 말합니다 . 그렇지 않으면 작동 할 것을 제안합니다. min 함수가 어떻게 든 뒤에서 주소를 호출 할 수 있습니다.


2
std::min매개 변수를 참조로 사용하므로 정의가 필요합니다.
Rakete1111

AE가 템플릿 클래스 AE <class T>이고 c7이 int가 아니라 T :: size_type이면 정의를 어떻게 작성합니까? 헤더에 "-1"로 초기화 된 값이 있지만 clang은 정의되지 않은 값을 말하고 정의를 작성하는 방법을 모릅니다.
Fabian

@Fabian 나는 여행 중이고 전화로 조금 바쁘다 ...하지만 나는 당신의 의견이 새로운 질문으로 쓰여지는 것이 가장 좋을 것 같다고 생각합니다. 발생한 오류를 포함 하는 MCVE 를 작성하고 gcc가 말하는 것을 던질 수도 있습니다. 나는 사람들이 당신에게 무엇이 무엇인지 빨리 말할 것입니다.
HostileFork은 그나마 신뢰 SE 말한다

@HostileFork : MCVE를 작성할 때 때때로 해결책을 스스로 알아냅니다. 제 경우에 대한 대답은 template<class K, class V, class C> const typename AE<K,V,C>::KeyContainer::size_type AE<K,V,C>::c7;KeyContainer가 std :: vector <K>의 typedef입니다. 종속 유형이므로 모든 템플릿 매개 변수를 나열하고 typename을 작성해야합니다. 누군가이 댓글이 유용하다고 생각할 것입니다. 그러나 이제 템플릿 클래스가 물론 헤더에 있기 때문에 DLL로 내보내는 방법이 궁금합니다. c7을 내 보내야합니까 ???
Fabian

24

이를 수행하는 또 다른 방법은 정수 유형의 경우 상수를 클래스에서 열거 형으로 정의하는 것입니다.

class test
{
public:
    enum { N = 10 };
};

2
그리고 이것은 아마도 문제를 해결할 것입니다. N이 min ()에 대한 매개 변수로 사용되면 기존 변수를 참조하려고 시도하는 대신 임시가 만들어집니다.
Edward Strange

이것은 비공개로 만들 수 있다는 장점이 있습니다.
Agostino

11

단지 int가 아닙니다. 그러나 클래스 선언에서 값을 정의 할 수 없습니다. 당신이 가지고 있다면:

class classname
{
    public:
       static int const N;
}

.h 파일에 다음이 있어야합니다.

int const classname::N = 10;

.cpp 파일에서.


2
클래스 선언 내에서 모든 유형의 변수를 선언 할 수 있다는 것을 알고 있습니다 . 정적 정수 상수도 클래스 선언 내부에 정의 될 수 있다고 생각했습니다 . 그렇지 않습니까? 그렇지 않다면 왜 컴파일러가 클래스 내에서 정의하려고 할 때 오류를 표시하지 않습니까? 또한 std :: cout 라인은 링커 오류를 일으키지 않지만 std :: min 라인은 왜 발생합니까?
HighCommander4 2010-06-11

아니요, 초기화시 코드가 생성되므로 클래스 선언에서 정적 멤버를 정의 할 수 없습니다. 코드도 내보내는 인라인 함수와 달리 정적 정의는 전역 적으로 고유합니다.
Amardeep AC9MF 2010 년

@ HighCommander4 : static const클래스 정의에서 정수 멤버에 대한 이니셜 라이저를 제공 할 수 있습니다 . 그러나 그것은 여전히 그 구성원을 정의하지 않습니다 . 자세한 내용은 Noah Roberts 답변을 참조하십시오.
AnT 2010-06-11

9

문제를 해결하는 또 다른 방법은 다음과 같습니다.

std::min(9, int(test::N));

(나는 Crazy Eddie의 대답이 문제가 존재하는 이유를 올바르게 설명한다고 생각합니다.)


5
또는 심지어std::min(9, +test::N);
Tomilov Anatoliy

하지만 여기에 큰 질문이 있습니다.이 모든 것이 최적입니까? 나는 너희들에 대해 모르지만 정의를 건너 뛰는 나의 큰 매력은 그것이 const 정적을 사용하는 데 메모리와 오버 헤드를 차지하지 않아야한다는 것입니다.
Opux

6

C ++ 11부터 다음을 사용할 수 있습니다.

static constexpr int N = 10;

이것은 이론적으로 여전히 .cpp 파일에서 상수를 정의해야하지만 그 주소를 사용하지 않는 N한 컴파일러 구현에서 오류가 발생할 가능성은 거의 없습니다.).


그리고 예제에서와 같이 'const int &'유형의 인수로 값을 전달해야하는 경우 어떻게해야합니까? :-)
Wormer

잘 작동합니다. 그런 식으로 N을 인스턴스화 하지 않고 const 참조를 임시로 전달합니다. wandbox.org/permlink/JWeyXwrVRvsn9cBj
Carlo Wood

C ++ 17은 C ++ 14가 아닐 수도 있고, 이전 버전의 gcc 6.3.0 이하에서는 C ++ 17이 아닐 수도 있지만 표준이 아닙니다. 그러나 이것을 언급 해 주셔서 감사합니다.
Wormer

아 네, 맞아요. 나는 wandbox에서 C ++ 14를 시도하지 않았습니다. 오 글쎄요, 이것이 제가 "이론적으로 여전히 상수를 정의해야합니다"라고 말한 부분입니다. 따라서 '표준'이 아니라는 것이 맞습니다.
Carlo Wood

3

C ++에서는 정적 const 멤버를 클래스 내부에 정의 할 수 있습니다.

아니요, 3.1 §2에서는 다음과 같이 말합니다.

선언은 함수의 본문 (8.4)을 지정하지 않고 함수를 선언 하지 않는 한 정의이며 , 외부 지정자 (7.1.1) 또는 연결 사양 (7.5)을 포함하고 이니셜 라이저 나 함수 본문이 없으며 정적 데이터를 선언합니다. 클래스 정의 (9.4) 에서 멤버 , 클래스 이름 선언 (9.1), opaque-enum-declaration (7.2) 또는 typedef 선언 (7.1.3), using-declaration (7.3. 3) 또는 사용 지시문 (7.3.4).

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