development

헤더 파일에서 정적 const char *를 선언하는 방법은 무엇입니까?

big-blog 2020. 12. 28. 22:24
반응형

헤더 파일에서 정적 const char *를 선언하는 방법은 무엇입니까?


내 .cpp 파일을 사용할 수 있도록 헤더 파일에 상수 char *를 정의하고 싶습니다. 그래서 나는 이것을 시도했습니다.

private:
    static const char *SOMETHING = "sommething";

다음과 같은 컴파일러 오류가 발생합니다.

오류 C2864 : 'SomeClass :: SOMETHING': 정적 const 정수 데이터 멤버 만 클래스 내에서 초기화 될 수 있습니다.

저는 C ++를 처음 사용합니다. 여기서 무슨 일이 일어나고 있습니까? 이것이 왜 불법입니까? 그리고 어떻게 대안으로 할 수 있습니까?


정수 유형이 아닌 경우 번역 단위에서 정적 변수를 정의해야합니다.

헤더에서 :

private:
    static const char *SOMETHING;
    static const int MyInt = 8; // would be ok

.cpp 파일에서 :

const char *YourClass::SOMETHING = "something";

C ++ 표준, 9.4.2 / 4 :

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


왜 정수 유형에서만 허용되는지에 대한 OP의 질문에 대답합니다.

객체가 lvalue로 사용되는 경우 (즉, 스토리지에 주소가있는 것으로) "하나의 정의 규칙"(ODR)을 충족해야합니다. 즉, 하나의 번역 단위로만 정의되어야합니다. 컴파일러는 해당 개체를 정의 할 번역 단위를 결정할 수 없으며 결정하지 않을 것입니다. 이것은 귀하의 책임입니다. 으로 정의 당신이 그것을 정의하도록 당신은 단지 그것을 정의되지 않은 객체 곳을 실제로 컴파일러를 말하고있다 여기 에, 특정 번역 단위.

한편, C ++ 언어에서 정수 상수는 특별한 상태를 갖습니다. 정수 상수 표현식 (ICE)을 형성 할 수 있습니다. ICE에서 정수 상수는 객체가 아닌 일반 으로 사용됩니다 (즉, 그러한 정수 값이 저장소에 주소가 있는지 여부는 관련이 없습니다). 실제로 ICE는 컴파일 시간에 평가됩니다. 이러한 정수 상수의 사용을 용이하게하려면 해당 값이 전역 적으로 표시되어야합니다. 그리고 상수 자체는 실제로 저장소에 실제 장소가 필요하지 않습니다. 이 정수 상수 때문에 특별한 대우를 받았습니다. 헤더 파일에 이니셜 라이저를 포함 할 수있게되었고 정의를 제공해야하는 요구 사항이 완화되었습니다 (처음에는 사실상, 그 다음에는 결정적).

다른 상수 유형에는 이러한 속성이 없습니다. 다른 상수 유형은 사실상 항상 lvalue로 사용됩니다 (또는 최소한 ICE 또는 ICE와 유사한 것에 참여할 수 없음). 즉, 정의가 필요합니다. 나머지는 다음과 같습니다.


오류는 static const char*클래스 내에서 초기화 할 수 없다는 것 입니다. 여기서는 정수 변수 만 초기화 할 수 있습니다.

클래스에서 멤버 변수를 선언 한 다음 클래스 외부에서 초기화해야합니다.

// 헤더 파일

class Foo {
    static const char *SOMETHING;
    // rest of class
};

// cpp 파일

const char *Foo::SOMETHING = "sommething";

이것이 성가신 것 같으면 초기화가 하나의 번역 단위에만 나타날 수 있기 때문이라고 생각하십시오. 클래스 정의에있는 경우 일반적으로 여러 파일에 포함됩니다. 상수 정수는 특수한 경우이며 (오류 메시지가 명확하지 않을 수 있음을 의미) 컴파일러는 변수 사용을 정수 값으로 효과적으로 대체 할 수 있습니다.

반대로 char*변수는 실제로 존재해야하는 메모리의 실제 객체를 가리키며 객체를 존재하게 만드는 정의 (초기화 포함)입니다. "단일 정의 규칙"은 헤더에 넣지 않음을 의미합니다. 그러면 해당 헤더를 포함한 모든 번역 단위에 정의가 포함되기 때문입니다. 현재 C ++ 규칙에 따라 동일한 이름을 가진 두 개의 다른 개체를 정의했으며 이는 합법적이지 않기 때문에 문자열에 동일한 문자가 포함되어 있어도 함께 연결할 수 없습니다. 그들이 같은 캐릭터를 가지고 있다는 사실이 합법적이지 않습니다.


C ++ 11에서는 constexpr키워드를 사용하고 헤더에 쓸 수 있습니다 .

private:
    static constexpr const char* SOMETHING = "something";


메모:

  • constexprSOMETHING당신이 쓸 수 없도록 상수 포인터를 만듭니다

    SOMETHING = "something different";
    

    나중에.

  • 컴파일러에 따라 .cpp 파일에 명시 적 정의를 작성해야 할 수도 있습니다.

    constexpr const char* MyClass::SOMETHING;
    

class A{
public:
   static const char* SOMETHING() { return "something"; }
};

특히 값 비싼 const 기본 매개 변수의 경우 항상 수행합니다.

class A{
   static
   const expensive_to_construct&
   default_expensive_to_construct(){
      static const expensive_to_construct xp2c(whatever is needed);
      return xp2c;
   }
};

Visual C ++를 사용하는 경우 링커에 대한 힌트를 사용하여 이식 불가능하게 수행 할 수 있습니다.

// In foo.h...

class Foo
{
public:
   static const char *Bar;
};

// Still in foo.h; doesn't need to be in a .cpp file...

__declspec(selectany)
const char *Foo::Bar = "Blah";

__declspec(selectany)Foo::Bar여러 개체 파일에서 선언 되더라도 링커는 하나만 선택합니다.

이것은 Microsoft 도구 모음에서만 작동합니다. 이것이 이식 가능할 것이라고 기대하지 마십시오.


H 파일 전용 상수를 제공하기 위해 템플릿과 함께 사용할 수있는 트릭이 있습니다.

(참고, 이것은 추악한 예이지만 적어도 g ++ 4.6.1에서는 그대로 작동합니다.)

(values.hpp 파일)

#include <string>

template<int dummy>
class tValues
{
public:
   static const char* myValue;
};

template <int dummy> const char* tValues<dummy>::myValue = "This is a value";

typedef tValues<0> Values;

std::string otherCompUnit(); // test from other compilation unit

(main.cpp)

#include <iostream>
#include "values.hpp"

int main()
{
   std::cout << "from main: " << Values::myValue << std::endl;
   std::cout << "from other: " << otherCompUnit() << std::endl;
}

(other.cpp)

#include "values.hpp"

std::string otherCompUnit () {
   return std::string(Values::myValue);
}

컴파일 (예 : g ++ -o main main.cpp other.cpp && ./main)하고 헤더에 선언 된 동일한 상수를 참조하는 두 개의 컴파일 단위를 확인합니다.

from main: This is a value
from other: This is a value

In MSVC, you may instead be able to use __declspec(selectany)

For example:

__declspec(selectany) const char* data = "My data";

Constant initializer allowed by C++ Standard only for integral or enumeration types. See 9.4.2/4 for details:

If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression (5.19). In that case, the member can appear in integral constant expressions. The member shall still be defined in a name- space scope if it is used in the program and the namespace scope definition shall not contain an initializer.

And 9.4.2/7:

Static data members are initialized and destroyed exactly like non-local objects (3.6.2, 3.6.3).

So you should write somewhere in cpp file:

const char* SomeClass::SOMETHING = "sommething";

To answer the why question, integral types are special in that they are not a reference to an allocated object but rather values that are duplicated and copied. It's just an implementation decision made when the language was defined, which was to handle values outside the object system and in as efficient and "inline" a fashion as possible.

This doesn't exactly explain why they are allowed as initializors in a type, but think of it as essentially a #define and then it will make sense as part of the type and not part of the object.

ReferenceURL : https://stackoverflow.com/questions/1639154/how-to-declare-a-static-const-char-in-your-header-file

반응형