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


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.