상수가 아닌 정적 멤버 또는 클래스의 정적 배열을 초기화 할 수없는 이유는 무엇입니까?


116

클래스에서 상수가 아닌 static멤버 또는 static배열을 초기화 할 수없는 이유는 무엇 입니까?

class A
{
    static const int a = 3;
    static int b = 3;
    static const int c[2] = { 1, 2 };
    static int d[2] = { 1, 2 };
};

int main()
{
    A a;

    return 0;
}

컴파일러는 다음 오류를 발행합니다.

g++ main.cpp
main.cpp:4:17: error: ISO C++ forbids in-class initialization of non-const static member b
main.cpp:5:26: error: a brace-enclosed initializer is not allowed here before ‘{’ token
main.cpp:5:33: error: invalid in-class initialization of static data member of non-integral type const int [2]’
main.cpp:6:20: error: a brace-enclosed initializer is not allowed here before ‘{’ token
main.cpp:6:27: error: invalid in-class initialization of static data member of non-integral type int [2]’

두 가지 질문이 있습니다.

  1. static클래스에서 데이터 멤버를 초기화 할 수없는 이유는 무엇 입니까?
  2. static클래스에서 const배열 , 심지어 배열을 초기화 할 수없는 이유는 무엇 입니까?

1
주된 이유는 바로 잡기가 까다롭기 때문이라고 생각합니다. 원칙적으로 당신은 당신이 말하는 것을 할 수 있지만, 이상한 부작용이있을 것입니다. 배열 예제가 허용 된 경우처럼 A :: c [0] 값을 얻을 수 있지만 A :: c를 함수에 전달할 수는 없습니다. 주소와 컴파일 시간이 필요하기 때문입니다. 상수에는 주소가 없습니다. C ++ 11은 constexpr을 사용하여이 중 일부를 가능하게했습니다.
Vaughn Cato

훌륭한 질문과 makred 답변. 도움이 된 링크 : msdn.microsoft.com/en-us/library/0e5kx78b.aspx
ETFovac

답변:


144

static클래스에서 데이터 멤버를 초기화 할 수없는 이유는 무엇 입니까?

C ++ 표준에서는 정적 상수 정수 또는 열거 유형 만 클래스 내에서 초기화 할 수 있습니다. 이것이 a다른 것들은 초기화되지 않고 초기화가 허용 되는 이유 입니다.

참조 :
C ++ 03 9.4.2 정적 데이터 멤버
§4

정적 데이터 멤버가 const 정수 또는 const 열거 형인 경우 클래스 정의의 선언은 정수 상수 표현식 (5.19)이 될 상수 초기화 프로그램을 지정할 수 있습니다. 이 경우 멤버는 정수 상수 식에 나타날 수 있습니다. 멤버는 프로그램에서 사용되는 경우 네임 스페이스 범위에서 정의되어야하며 네임 스페이스 범위 정의에는 이니셜 라이저가 포함되지 않아야합니다.

정수 유형이란 무엇입니까?

C ++ 03 3.9.1 기본 유형
§7

bool, char, wchar_t 유형과 부호있는 정수 유형과 부호없는 정수 유형을 총칭하여 정수 유형이라고합니다 .43) 정수 유형의 동의어는 정수 유형입니다.

각주:

43) 따라서 열거 형 (7.2)은 정수가 아닙니다. 그러나 열거 형은 4.5에 지정된대로 int, unsigned int, long 또는 unsigned long으로 승격 될 수 있습니다.

해결 방법 :

열거 형 트릭 을 사용 하여 클래스 정의 내에서 배열을 초기화 할 수 있습니다 .

class A 
{
    static const int a = 3;
    enum { arrsize = 2 };

    static const int c[arrsize] = { 1, 2 };

};

표준이 이것을 허용하지 않는 이유는 무엇입니까?

Bjarne은 여기에서 이것을 적절하게 설명합니다 .

클래스는 일반적으로 헤더 파일에서 선언되고 헤더 파일은 일반적으로 많은 변환 단위에 포함됩니다. 그러나 복잡한 링커 규칙을 피하기 위해 C ++에서는 모든 개체에 고유 한 정의가 있어야합니다. C ++에서 객체로 메모리에 저장해야하는 엔티티의 클래스 내 정의를 허용하면 해당 규칙이 깨집니다.

static const정수 유형 및 열거 형 만 클래스 내 초기화가 허용되는 이유는 무엇 입니까?

이에 대한 답은 Bjarne의 인용문에 숨겨져 있습니다.
"C ++에서는 모든 객체에 고유 한 정의가 있어야합니다. C ++에서 객체로 메모리에 저장해야하는 엔티티의 클래스 내 정의를 허용하면 해당 규칙이 깨질 것입니다."

단지 참고 static const정수가 컴파일시 상수로 취급 될 수있다. 컴파일러는 정수 값이 언제든지 변경되지 않으므로 자체 마법을 적용하고 최적화를 적용 할 수 있다는 것을 알고 있습니다. 컴파일러는 이러한 클래스 멤버를 인라인하기 만하면됩니다. 즉, 더 이상 메모리에 저장되지 않으므로 메모리에 저장해야 할 필요성이 제거됩니다. , Bjarne이 언급 한 규칙에 대한 예외를 이러한 변수에 제공합니다.

여기서 주목할 점은 static const적분 값이 In-Class Initialization을 가질 수 있더라도 이러한 변수의 주소를 가져 오는 것은 허용되지 않습니다. 클래스 밖의 정의가있는 경우에만 정적 멤버의 주소를 가져올 수 있습니다.

열거 형의 값을 정수가 예상되는 곳에 사용할 수 있기 때문에 열거 형이 허용됩니다. 위의 인용 참조


이것이 C ++ 11에서 어떻게 변경됩니까?

C ++ 11은 제한을 어느 정도 완화합니다.

C ++ 11 9.4.2 정적 데이터 멤버
§3

정적 데이터 멤버가 const 리터럴 유형 인 경우 클래스 정의의 선언 은 할당 식인 모든 initializer-clause 가 상수 식인 중괄호 또는 같음 이니셜 라이저 를 지정할 수 있습니다 . 리터럴 유형의 정적 데이터 멤버는 클래스 정의에서 선언 될 수 있습니다. 그렇다면 해당 선언 은 할당 표현식 인 모든 initializer-clause 가 있는 중괄호 또는 같음 이니셜 라이저지정해야합니다.constexpr specifier;상수 표현식입니다. [참고 :이 두 경우 모두 멤버가 상수 표현식으로 나타날 수 있습니다. —end note] 멤버는 프로그램에서 사용되는 경우 네임 스페이스 범위에서 정의되어야하며 네임 스페이스 범위 정의에는 이니셜 라이저가 포함되지 않아야합니다.

또한, C ++ 11 것이다 가 선언되는 경우 비 정적 데이터 부재 (동급) 초기화되도록 (§12.6.2.8)을 허용한다. 이것은 훨씬 쉬운 사용자 의미를 의미합니다.

이러한 기능은 아직 최신 gcc 4.7에서 구현되지 않았으므로 여전히 컴파일 오류가 발생할 수 있습니다.


7
C ++ 11에서는 상황이 다릅니다. 답변은 업데이트를 사용할 수 있습니다.
bames53

4
이것은 사실이 아닌 것 같습니다. "정적 const 정수만 컴파일 시간 상수로 처리 될 수 있습니다. 컴파일러는 정수 값이 언제든지 변경되지 않으므로 자체 마법을 적용하고 최적화를 적용 할 수 있음을 알고 있습니다. 컴파일러는 간단히 인라인 같은 반원들, 즉 그들이 더 이상 메모리에 저장되지 않습니다 ", 당신은 확실히 그들이하고 있습니까 반드시 메모리에 저장되지? 회원에 대한 정의를 제공하면 어떻게됩니까? 무엇을 &member반환할까요?
Nawaz

2
@Als : 네. 그것이 제 질문입니다. 따라서 C ++에서 정수 유형에 대해서만 클래스 내 초기화를 허용하는 이유는 귀하의 대답으로 올바르게 대답되지 않습니다. static const char*멤버에 대한 초기화를 허용하지 않는 이유를 생각해보십시오 .
Nawaz

3
@Nawaz : C ++ 03 은 static 및 const 정수 및 const 열거 형에 대해 상수 이니셜 라이저 만 허용 하고 다른 유형 은 허용하지 않았기 때문에 C ++ 11은이를 const 리터럴 유형으로 확장 하여 클래스 내 초기화에 대한 표준을 완화합니다. C ++ 03에서는 아마도 변경을 보증하는 감독 이었으므로 C ++ 11에서 수정되었으므로 변경에 대한 전통적인 전술적 이유가 있으면 알지 못합니다. 그들.
Alok 저장

4
언급 한 "해결 방법"파는 g ++에서 작동하지 않습니다 .
iammilind 2010 년

4

이것은 단순한 링커의 옛 시절의 유물처럼 보입니다. 해결 방법으로 정적 메서드에서 정적 변수를 사용할 수 있습니다.

// header.hxx
#include <vector>

class Class {
public:
    static std::vector<int> & replacement_for_initialized_static_non_const_variable() {
        static std::vector<int> Static {42, 0, 1900, 1998};
        return Static;
    }
};

int compilation_unit_a();

// compilation_unit_a.cxx
#include "header.hxx"

int compilation_unit_a() {  
    return Class::replacement_for_initialized_static_non_const_variable()[1]++;
}

// main.cxx
#include "header.hxx"

#include <iostream>

int main() {
    std::cout
    << compilation_unit_a()
    << Class::replacement_for_initialized_static_non_const_variable()[1]++
    << compilation_unit_a()
    << Class::replacement_for_initialized_static_non_const_variable()[1]++
    << std::endl;
}

짓다:

g++ -std=gnu++0x -save-temps=obj -c compilation_unit_a.cxx 
g++ -std=gnu++0x -o main main.cxx compilation_unit_a.o

운영:

./main

이것이 작동한다는 사실 (클래스 정의가 다른 컴파일 단위에 포함 되더라도 일관되게)은 오늘날 링커 (gcc 4.9.2)가 실제로 충분히 똑똑하다는 것을 보여줍니다.

웃긴 : 0123arm과 3210x86에 인쇄 합니다 .


1

나는 당신이 선언과 정의를 섞는 것을 막기 위해서라고 생각합니다. (여러 위치에 파일을 포함하면 발생할 수있는 문제에 대해 생각해보십시오.)


0

A::a모든 번역 단위가 사용하는 정의는 하나만있을 수 있기 때문 입니다.

static int a = 3;모든 번역 단위에 포함 된 헤더의 클래스에서 수행 하면 여러 정의를 얻을 수 있습니다. 따라서 정적이 아닌 라인 외부 정의는 강제로 컴파일러 오류가됩니다.

이것을 사용 static inline하거나 static const구제하십시오. static inline번역 단위에서 사용되는 경우에만 기호를 구체화하고 comdat 그룹에 있기 때문에 여러 번역 단위로 정의 된 경우 링커가 하나의 복사본 만 선택하고 남겨 두도록합니다. const파일 범위에서 컴파일러는 기호를 방출 extern하지 않습니다. 클래스에서 허용 되지 않는 사용 되지 않는 한 코드에서 항상 즉시 대체되기 때문 입니다.

주의해야 할 점은 static inline int b;정의로 처리되는 반면 static const int b또는 static const A b;여전히 선언으로 처리되며 클래스 내에서 정의하지 않으면 라인 외부로 정의되어야합니다. 흥미롭게도 static constexpr A b;는 정의로 취급되는 반면는 static constexpr int b;오류이고 이니셜 라이저가 있어야합니다 (이제 정의가되고 파일 범위의 모든 const / constexpr 정의와 마찬가지로 int에 클래스 유형이없는 이니셜 라이저가 필요하기 때문입니다) = A()정의 일 때 암시 적이기 때문 입니다. clang에서는이를 허용하지만 gcc에서는 명시 적으로 초기화해야하거나 오류입니다. 대신 인라인에서는 문제가되지 않습니다.) static const A b = A();허용되지 않으며 constexpr또는 이어야합니다.inline클래스 유형의 정적 객체에 대한 이니셜 라이저를 허용하기 위해, 즉 선언 이상의 클래스 유형의 정적 멤버를 만들 수 있습니다. 따라서 특정 상황에서 예는 A a;명시 적으로 초기화하는 것과 동일하지 않습니다A a = A();(전자는 선언이 될 수 있지만 해당 유형에 대해 선언 만 허용되는 경우 후자는 오류입니다. 후자는 정의에서만 사용할 수 있습니다. 정의로 constexpr만듭니다). constexpr기본 생성자를 사용 하고 지정하는 경우 생성자는constexpr

#include<iostream>

struct A
{
    int b =2;
    mutable int c = 3; //if this member is included in the class then const A will have a full .data symbol emitted for it on -O0 and so will B because it contains A.
    static const int a = 3;
};

struct B {
    A b;
    static constexpr A c; //needs to be constexpr or inline and doesn't emit a symbol for A a mutable member on any optimisation level
};

const A a;
const B b;

int main()
{
    std::cout << a.b << b.b.b;
    return 0;
}

정적 멤버는 extern int A::a;명시 적 파일 범위 선언 (클래스에서만 만들 수 있으며 라인 외부 정의는 클래스의 정적 멤버를 참조해야하며 정의 여야하며 extern을 포함 할 수 없음) 인 반면 비 정적 멤버는 다음의 일부입니다. 클래스의 완전한 유형 정의이며 extern. 암시 적으로 정의됩니다. 따라서 int i[]; int i[5];재정의 static int i[]; int A::i[5];는 아니지만 2 개의 extern과 달리 static int i[]; static int i[5];클래스에서 수행 하는 경우 컴파일러는 여전히 중복 멤버를 감지합니다 .


-3

정적 변수는 클래스에 따라 다릅니다. 생성자는 인스턴스에 대한 ESPECIALY 속성을 초기화합니다.

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