정적 상수 문자열 (클래스 멤버)


445

클래스 (이 경우 shape-factory)에 대한 개인 정적 상수를 갖고 싶습니다.

나는 일종의 무언가를 갖고 싶습니다.

class A {
   private:
      static const string RECTANGLE = "rectangle";
}

불행히도 C ++ (g ++) 컴파일러에서 다음과 같은 모든 종류의 오류가 발생합니다.

ISO C ++에서 멤버 'RECTANGLE'의 초기화를 금지합니다.

비 통합 유형 'std :: string'의 정적 데이터 멤버의 클래스 내부 초기화가 올바르지 않습니다.

오류 : 'RECTANGLE'을 정적으로 만들기

이것은 이런 종류의 멤버 디자인이 표준을 준수하지 않는다는 것을 알려줍니다. #define 지시어를 사용하지 않고 어떻게 개인 리터럴 상수 (또는 아마도 공용)를 가지고 있습니까 (데이터 전역의 추악함을 피하고 싶습니다!)

도움을 주시면 감사하겠습니다.


15
모든 훌륭한 답변에 감사드립니다! 오래 산다!
lb. 09.

누군가 '통합'유형이 무엇인지 말해 줄 수 있습니까? 대단히 감사합니다.
lb. 09.

1
정수 유형은 정수를 나타내는 유형을 나타냅니다. 참조 publib.boulder.ibm.com/infocenter/comphelp/v8v101/...
bleater

팩토리의 전용 정적 문자열은 좋은 해결책이 아닙니다. 팩토리 클라이언트는 지원되는 모양을 알아야하므로 프라이빗 정적 상태로 유지하는 대신 별도의 네임 스페이스에 정적 const std :: string RECTANGLE = "Rectangle ".
LukeCodeBaker

수업이 템플릿 수업 인 경우 stackoverflow.com/q/3229883/52074
Trevor Boyd Smith

답변:


471

클래스 정의 외부에서 정적 멤버를 정의하고 거기에 이니셜 라이저를 제공해야합니다.

먼저

// In a header file (if it is in a header file in your case)
class A {   
private:      
  static const string RECTANGLE;
};

그리고

// In one of the implementation files
const string A::RECTANGLE = "rectangle";

원래 사용하려는 구문 (클래스 정의의 초기화 프로그램)은 정수 및 열거 형 유형에만 허용됩니다.


C ++ 17부터는 원래 선언과 매우 유사한 또 다른 옵션 인 인라인 변수가 있습니다.

// In a header file (if it is in a header file in your case)
class A {   
private:      
  inline static const string RECTANGLE = "rectangle";
};

추가 정의가 필요하지 않습니다.

또는 대신 이 변형으로 const선언 할 수 있습니다 constexpr. 을 의미 inline하므로 더 이상 명시 적이 필요하지 않습니다 .constexprinline


8
또한 STL 문자열을 사용할 필요가없는 경우 const char * 만 정의하면됩니다. (적은 오버 헤드)
KSchmidt

50
나는 그것이 항상 오버 헤드가 적다는 것을 확신하지 못합니다-사용법에 달려 있습니다. 이 멤버가 const 문자열 &를 취하는 함수에 인수로 전달되는 경우 초기화 중에 각 호출마다 하나의 문자열 객체 생성과 임시 호출이 생성됩니다. 정적 문자열 객체를 만들기위한 IMHO 오버 헤드는 무시할 수 있습니다.
Tadeusz Kopec

23
오히려 std :: string을 항상 사용하고 싶습니다. 오버 헤드는 무시할 만하지 만, 훨씬 더 많은 옵션이 있으며 "magic"== A :: RECTANGLE과 같은 바보 같은 것들을 작성하여 주소를 비교할 가능성은 훨씬 적습니다 ...
Matthieu M.

9
char const*모든 동적 초기화가 완료되기 전에 초기화된다는 장점이있다. 따라서 모든 객체의 생성자에서 RECTANGLE이미 초기화 된 것에 의존 할 수 있습니다 .
Johannes Schaub-litb

8
@cirosantilli : C ++ 이니셜 라이저는 처음부터 선언이 아닌 정의의 일부이기 때문에 시작했습니다 . 그리고 클래스 내부의 데이터 멤버 선언은 선언입니다. (반면 const 상수 와 열거 형 멤버, C ++ 11에서는 리터럴 타입 의 const 멤버에 대해서는 예외가 발생했습니다 .)
AnT

153

C ++ 11에서는 다음을 수행 할 수 있습니다.

class A {
 private:
  static constexpr const char* STRING = "some useful string constant";
};

30
불행히도이 솔루션은 std :: string에서 작동하지 않습니다.
HelloWorld

2
1. 이것은 리터럴에서만 작동하며 2. Gnu / GCC는 벌금을 준수하지만 다른 컴파일러는 오류를 발생 시키지만 표준을 준수하지는 않습니다. 정의는 본문에 있어야합니다.
ManuelSchneid3r

2
@ ManuelSchneid3r 이것이 어떻게 "표준이 아닌"것입니까? 그것은 bog 표준 C ++ 11 중괄호 또는 동등한 초기화 처럼 보입니다 .
underscore_d

3
@rvighne, 맞지 않습니다. 타입이 아니라 var를 constexpr의미 const합니다. 즉 static constexpr const char* const과 동일 static constexpr const char*,하지만 같은 static constexpr char*.
midenok

2
@ abyss.7-귀하의 답변에 감사드립니다. 다른 질문이 있습니다. 왜 정적이어야합니까?
가이 Avraham

34

클래스 정의 내에서는 정적 멤버 만 선언 할 수 있습니다 . 클래스 외부에서 정의 해야합니다 . 컴파일 타임 적분 상수의 경우 표준은 멤버를 "초기화"할 수 있다는 예외를 만듭니다. 그래도 정의는 아닙니다. 예를 들어 주소를 정의하지 않으면 작동하지 않습니다.

상수에 const char [] 대신 std :: string을 사용하면 이점이 없다고 언급하고 싶습니다 . std :: string은 훌륭하지만 모두 동적 초기화가 필요합니다. 그래서, 당신이 같은 것을 쓰면

const std::string foo = "hello";

네임 스페이스 범위에서 foo의 생성자는 기본 시작 실행 직전에 실행되며이 생성자는 힙 메모리에 상수 "hello"의 복사본을 만듭니다. 실제로 RECTANGLE이 std :: string이어야하는 경우가 아니라면

// class definition with incomplete static member could be in a header file
class A {
    static const char RECTANGLE[];
};

// this needs to be placed in a single translation unit only
const char A::RECTANGLE[] = "rectangle";

그곳에! 힙 할당 없음, 복사 없음, 동적 초기화 없음

건배


1
이것은 C ++ 11 이전의 답변입니다. 표준 C ++을 사용하고 std :: string_view를 사용하십시오.

1
C ++ 11에는 std :: string_view가 없습니다.
루카스 살 리치

17

이것은 추가 정보 일 뿐이지 만 실제로 헤더 파일에 문자열을 원하면 다음과 같이 시도하십시오.

class foo
{
public:
    static const std::string& RECTANGLE(void)
    {
        static const std::string str = "rectangle";

        return str;
    }
};

나는 그것이 권장되는 것을 의심하지만.


멋지다 :)-C ++ 이외의 다른 언어로 된 배경이 있다고 생각합니까?
lb. 09.

5
나는 그것을 권장하지 않습니다. 나는 이것을 자주한다. 잘 작동하고 구현 파일에 문자열을 넣는 것보다 더 분명합니다. std :: string의 실제 데이터는 여전히 힙에 있습니다. const char *를 반환합니다.이 경우 정적 변수를 선언 할 필요가 없으므로 선언이 공간을 덜 차지합니다 (코드 단위). 그래도 맛의 문제.
Zoomulator

15

C ++ 17에서는 인라인 변수를 사용할 수 있습니다 .

class A {
 private:
  static inline const std::string my_string = "some useful string constant";
};

이것은 abyss.7의 답변 과 다릅니다 : 이것은 실제 std::string객체를 정의합니다 .const char*


사용 inline하면 많은 중복이 생성 된다고 생각하지 않습니까?
shuva


8

클래스 내 초기화 구문을 사용하려면 상수는 상수 표현식으로 초기화 된 정수 또는 열거 유형의 정적 const 여야합니다.

이것이 제한 사항입니다. 따라서이 경우 클래스 외부에서 변수를 정의해야합니다. @AndreyT의 답변을 참조하십시오


7

클래스 정적 변수는 헤더에서 선언 할 수 있지만 .cpp 파일에서 정의 해야 합니다. 정적 변수의 인스턴스는 하나만있을 수 있고 컴파일러는 생성 할 객체 파일을 결정할 수 없으므로 대신 결정을 내려야합니다.

C ++ 11의 선언으로 정적 값의 정의를 유지하기 위해 중첩 된 정적 구조를 사용할 수 있습니다. 이 경우 정적 멤버는 구조이며 .cpp 파일에 정의되어야하지만 값은 헤더에 있습니다.

class A
{
private:
  static struct _Shapes {
     const std::string RECTANGLE {"rectangle"};
     const std::string CIRCLE {"circle"};
  } shape;
};

개별 멤버를 초기화하는 대신 전체 정적 구조가 .cpp로 초기화됩니다.

A::_Shapes A::shape;

값은

A::shape.RECTANGLE;

또는-회원은 비공개이며 A에서만 사용할 수 있으므로

shape.RECTANGLE;

이 솔루션은 여전히 ​​정적 변수의 초기화 순서 문제가 있습니다. 정적 값을 사용하여 다른 정적 변수를 초기화하면 첫 번째 변수는 아직 초기화되지 않을 수 있습니다.

// file.h
class File {
public:
  static struct _Extensions {
    const std::string h{ ".h" };
    const std::string hpp{ ".hpp" };
    const std::string c{ ".c" };
    const std::string cpp{ ".cpp" };
  } extension;
};

// file.cpp
File::_Extensions File::extension;

// module.cpp
static std::set<std::string> headers{ File::extension.h, File::extension.hpp };

이 경우 정적 변수 헤더 에는 링커에서 만든 초기화 순서에 따라 { ""} 또는 { ".h", ".hpp"}가 포함됩니다.

@ abyss.7에서 언급했듯이 constexpr변수 값을 컴파일 타임에 계산할 수있는 경우 에도 사용할 수 있습니다. 그러나 문자열을 선언 static constexpr const char*하고 프로그램에서 std::string달리 std::string사용하면 상수를 사용할 때마다 새 객체가 생성 되므로 오버 헤드 가 발생합니다.

class A {
public:
   static constexpr const char* STRING = "some value";
};
void foo(const std::string& bar);
int main() {
   foo(A::STRING); // a new std::string is constructed and destroyed.
}

잘 준비된 답변 Marko. 두 가지 세부 사항 : 하나는 정적 클래스 멤버에 대해 cpp 파일이 필요하지 않으며 모든 종류의 상수에 std :: string_view를 사용하십시오.


4

가능한 그냥 :

static const std::string RECTANGLE() const {
    return "rectangle";
} 

또는

#define RECTANGLE "rectangle"

11
입력 된 상수를 사용할 수있을 때 #define을 사용하는 것은 잘못입니다.
Artur Czajka

첫 번째 예제는 기본적으로 좋은 솔루션 constexpr이지만 정적 기능을 만들 수없는 경우 좋은 해결책 const입니다.
Frank Puffer

이 솔루션은 피해야합니다. 모든 호출에서 새 문자열을 작성합니다. 이 것이 더 나은 :static const std::string RECTANGLE() const { static const std::string value("rectangle"); return value; }
오즈 솔로몬

완전한 컨테이너를 리턴 값으로 사용하는 이유는 무엇입니까? std :: string_vew를 사용하십시오.이 경우 컨텐츠는 계속 유효합니다. 문자열 리터럴을 사용하여 문자열보기를 만들고 반환하는 것이 더 좋습니다 ... 그리고 마지막으로 const 반환 값은 의미가 없거나 영향을 미치지 않습니다 ..ah 그렇습니다. 네임 스페이스 이름을 ... 그리고 그것은 constexpr로 확인하시기 바랍니다

4

const char*위에서 언급 한 솔루션 을 사용할 수 있지만 항상 문자열이 필요한 경우 많은 오버 헤드가 발생합니다.
반면 정적 문자열에는 동적 초기화가 필요하므로 다른 전역 / 정적 변수를 초기화하는 동안 값을 사용하려면 초기화 순서 문제가 발생할 수 있습니다. 이를 피하기 위해 가장 저렴한 것은 객체가 초기화되었는지 여부를 확인하는 getter를 통해 정적 문자열 객체에 액세스하는 것입니다.

//in a header  
class A{  
  static string s;   
public:   
  static string getS();  
};  
//in implementation  
string A::s;  
namespace{  
  bool init_A_s(){  
    A::s = string("foo");   
    return true;  
  }  
  bool A_s_initialized = init_A_s();  
}  
string A::getS(){      
  if (!A_s_initialized)  
    A_s_initialized = init_A_s();  
  return s;  
}  

를 사용해야 A::getS()합니다. 모든 스레딩은로만 시작할 수 main()있고 A_s_initialized이전 main()에 초기화되기 때문에 다중 스레드 환경에서도 잠금이 필요하지 않습니다. A_s_initialized기본적으로 0입니다 (동적 초기화 전).getS() s를 초기화하기 전에 하면 init 함수를 안전하게 호출합니다.

Btw, 위의 대답 : " static const std :: string RECTANGLE () const ", 정적 함수는 const객체가 있으면 상태를 변경할 수 없으므로 (이 포인터가 없음) 정적 함수는 불가능합니다 .


4

2018 및 C ++ 17로 빨리 감기

  • std :: string을 사용하지 말고 std :: string_view 리터럴을 사용하십시오.
  • 'constexpr'벨로우즈에 주목하십시오. 이것은 "컴파일 타임"메커니즘이기도합니다.
  • 인라인이 없다는 것은 반복을 의미하지 않습니다
  • 이를 위해 cpp 파일이 필요하지 않습니다
  • 컴파일시에만 static_assert 'works'

    using namespace std::literals;
    
    namespace STANDARD {
    constexpr 
    inline 
    auto 
    compiletime_static_string_view_constant() {
    // make and return string view literal
    // will stay the same for the whole application lifetime
    // will exhibit standard and expected interface
    // will be usable at both
    // runtime and compile time
    // by value semantics implemented for you
        auto when_needed_ =  "compile time"sv;
        return when_needed_  ;
    }

    };

위의 내용은 합법적이고 표준적인 C ++ 시민입니다. 알고리즘, 컨테이너, 유틸리티 등 모든 표준에 쉽게 참여할 수 있습니다. 예를 들면 다음과 같습니다.

// test the resilience
auto return_by_val = []() {
    auto return_by_val = []() {
        auto return_by_val = []() {
            auto return_by_val = []() {
return STANDARD::compiletime_static_string_view_constant();
            };
            return return_by_val();
        };
        return return_by_val();
    };
    return return_by_val();
};

// actually a run time 
_ASSERTE(return_by_val() == "compile time");

// compile time 
static_assert(
   STANDARD::compiletime_static_string_view_constant() 
   == "compile time" 
 );

표준 C ++를 즐기십시오


모든 함수에서 매개 변수 std::string_view를 사용하는 경우에만 상수에 사용하십시오 string_view. 함수 중 하나가 const std::string&매개 변수를 사용하는 경우 string_view해당 매개 변수를 통해 상수를 전달하면 문자열 복사본이 만들어 집니다. 상수가 유형 인 std::string경우 const std::string&매개 변수 또는 std::string_view매개 변수에 대해 사본이 작성되지 않습니다 .
Marko Mahnič

좋은 대답이지만 string_view가 함수에서 반환되는 이유가 궁금하십니까? 이러한 종류의 트릭은 inline변수가 ODR 의미와 함께 C ++ 17에 도착 하기 전에 유용 했습니다. 그러나 string_view 그래서 그냥, 너무 ++ 17 C 인 constexpr auto some_str = "compile time"sv;작업을 수행합니다 (실제로, 그것은 아니에요 변수, 그건 constexpr너무, inline암시 적이다, 당신은 변수가있는 경우 - 즉 없음 constexpr- 다음 inline auto some_str = "compile time"sv;, 그것을 할 것입니다하지만 물론 네임 스페이스 범위 본질적으로 전역 변수 인 변수는 거의 좋은 아이디어가 아닙니다.
정신 상실
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.