C ++에서 개인 정적 멤버를 초기화하는 방법은 무엇입니까?


519

C ++에서 비공개 정적 데이터 멤버를 초기화하는 가장 좋은 방법은 무엇입니까? 헤더 파일에서 시도했지만 이상한 링커 오류가 발생합니다.

class foo
{
    private:
        static int i;
};

int foo::i = 0;

클래스 외부에서 개인 멤버를 초기화 할 수 없기 때문에 이것이 추측됩니다. 가장 좋은 방법은 무엇입니까?


2
안녕 제이슨 정적 멤버 (특히 통합 멤버)의 기본 초기화에 대한 주석을 찾지 못했습니다. 실제로 링커가 찾을 수 있도록 int foo :: i를 작성해야하지만 0으로 자동 초기화됩니다! 이 줄이면 충분합니다 : int foo :: i; (이것은 정적 메모리에 저장된 모든 객체에 유효합니다. 링커는 정적 객체를 초기화해야합니다.)
Nico

1
아래 답변은 템플릿 클래스에는 적용되지 않습니다. 초기화는 소스 파일로 이동해야합니다. 템플릿 클래스의 경우 불가능하거나 필요하지 않습니다.
Joachim W

7
C ++ 17은 정적 데이터 멤버의 인라인 초기화를 허용합니다 (정수가 아닌 유형의 경우에도) inline static int x[] = {1, 2, 3};. en.cppreference.com/w/cpp/language/static#Static_data_members
Vladimir Reshetnikov를

답변:


556

클래스 선언은 헤더 파일 (또는 공유되지 않은 경우 소스 파일)에 있어야합니다.
파일 : foo.h

class foo
{
    private:
        static int i;
};

그러나 초기화는 소스 파일에 있어야합니다.
파일 : foo.cpp

int foo::i = 0;

초기화가 헤더 파일에있는 경우 헤더 파일을 포함하는 각 파일에는 정적 멤버의 정의가 있습니다. 따라서 변수를 초기화하는 코드가 여러 소스 파일에 정의되므로 링크 단계에서 링커 오류가 발생합니다. 의 초기화는 static int i기능 외부에서 수행해야합니다.

주 : 매트 커티스 : 아웃 포인트 C는 ++의 단순화를 허용하는, 상기 정적 부재 변수 CONST INT 형인 경우 (예를 들어 int, bool, char). 그런 다음 헤더 파일의 클래스 선언 내에서 직접 멤버 변수를 선언하고 초기화 할 수 있습니다.

class foo
{
    private:
        static int const i = 42;
};

4
예. 그러나 질문이 단순화되었다고 가정합니다. 기술적으로 선언과 정의는 모두 단일 소스 파일에있을 수 있습니다. 그러나 이것은 다른 클래스에 의한 클래스 사용을 제한합니다.
Martin York

11
실제로뿐만 아니라 POD, 그것은뿐만 아니라 정수형이어야한다 (INT, 단기, 부울, 문자 ...)
매트 커티스

9
이것은 값이 어떻게 초기화되는지에 대한 질문이 아닙니다. 이와 같이 정의 된 const 정수 유형은 구현에 의해 컴파일 타임 상수로 바뀔 수 있습니다. 바이너리 의존성을 높이기 때문에 항상 원하는 것은 아닙니다. 값이 변경되면 클라이언트 코드를 다시 컴파일해야합니다.
Steve Jessop

5
@Martin : 수정 s / POD / integral type / 외에도 주소를 가져 오면 정의도 있어야합니다. 이상하게 들리 겠지만 클래스 정의에서 이니셜 라이저를 사용한 선언은 정의가 아닙니다. 템플릿 CONST 관용구는 당신이 헤더 파일의 정의를 필요로하는 경우에 대한 해결 방법을 제공합니다. 또 다른 간단한 해결 방법은 로컬 정적 상수의 값을 생성하는 함수입니다. 건배 & hth.
건배와 hth. -Alf

3
int foo :: i = 0; 함수 내부에 있지 않아야합니다 (주 함수 포함). 나는 내 주요 기능의 시작 부분에 있었고 그것을 좋아하지 않습니다.
qwerty9967

89

A에 대한 변수 :

foo.h :

class foo
{
private:
    static int i;
};

foo.cpp :

int foo::i = 0;

foo::i프로그램에 인스턴스가 하나만있을 수 있기 때문 입니다. extern int i헤더 파일과 int i소스 파일 과 동일 합니다.

A의 상수 는 클래스 선언의 값을 바로 넣을 수 있습니다 :

class foo
{
private:
    static int i;
    const static int a = 42;
};

2
이것은 유효한 포인트입니다. 이것도 설명을 추가하겠습니다. 그러나 이것은 POD 유형에서만 작동합니다.
Martin York

언제부터 C ++은 클래스 내 선언과 정수 유형에 대한 정의가 없으면 좋을 수 있습니다. C ++ 98 자체 또는 C ++ 03 이후 또는 언제? 본격적인 링크를 공유하십시오. C ++ 표준 표현은 컴파일러와 동기화되지 않습니다. 그들은 회원이 사용된다면 여전히 정의 될 것이라고 언급한다. 따라서 C ++ 표준 인용은 필요하지 않습니다.
smRaj

1
private변수가 클래스 외부에서 초기화 될 수있는 이유가 궁금 합니다. 정적이 아닌 변수에 대해서도이 작업을 수행 할 수 있습니까?
Krishna Oza

설명을 찾았습니까? @Krishna_Oza
nn0p

@ nn0p는 아직 없지만 외부의 정적이 아닌 개인 변수 초기화 Class는 Cpp에서 의미가 없습니다.
Krishna Oza

41

C ++ 17부터 정적 멤버는 인라인 키워드로 헤더에 정의 될 수 있습니다 .

http://en.cppreference.com/w/cpp/language/static

"정적 데이터 멤버는 인라인으로 선언 될 수 있습니다. 인라인 정적 데이터 멤버는 클래스 정의에서 정의 될 수 있으며 기본 멤버 이니셜 라이저를 지정할 수 있습니다. 클래스 외부 정의가 필요하지 않습니다."

struct X
{
    inline static int n = 1;
};

1
이것은 현재 새로운 표준이되고있는 C ++ 17부터 가능합니다.
Grebu

31

이 질문의 미래 시청자들을 위해, monkey0506이 제안하는 것을 피해야한다고 지적하고 싶습니다 .

헤더 파일은 선언 용입니다.

헤더 파일은 .cpp직접 또는 간접적으로 모든 파일에 대해 한 번 컴파일되며 #includes, 함수 외부의 코드는 프로그램 초기화시 이전에 실행됩니다 main().

foo::i = VALUE;헤더에 넣으면 모든 파일에 대해 foo:i값이 VALUE무엇이든 .cpp할당되며, 이러한 할당 main()은 실행 되기 전에 결정되지 않은 순서 (링커에 의해 결정됨) 로 수행됩니다.

파일 #define VALUE중 하나에서 다른 숫자가 되려면 어떻게해야 .cpp합니까? 잘 컴파일되고 프로그램을 실행할 때까지 어느 쪽이 이길 지 알 수 없습니다.

같은 이유로 당신이 결코의 헤더에 실행 코드를 삽입하지 마십시오 파일.#include.cpp

가드 포함 (항상 사용해야한다고 동의 함)은 다른 파일로부터 보호합니다 : #include단일 .cpp파일 을 컴파일하는 동안 동일한 헤더가 간접적으로 여러 번 d


2
클래스 템플릿의 경우를 제외하고는 이것에 대해 옳습니다 (물론 묻지 않지만 많은 것을 다루고 있습니다). 따라서 클래스가 완전히 정의되어 있고 클래스 템플릿이 아닌 경우 이러한 정적 멤버를 별도의 CPP 파일에 넣습니다. 그러나 클래스 템플릿의 경우 정의는 동일한 번역 단위 (예 : 헤더 파일)에 있어야합니다.
monkey0506

@ monkey_05_06 : 템플릿 코드에서 정적 멤버를 피하기위한 인수 인 것 같습니다. 클래스의 인스턴스화마다 하나의 정적 멤버로 끝납니다. 헤더를 여러 cpp 파일로 컴파일하면 문제가 악화됩니다. 충돌하는 정의의 뗏목을 얻을 수 있습니다.
Joshua Clayton

publib.boulder.ibm.com/infocenter/macxhelp/v6v81/… 이 링크는 기본 기능으로 정적 템플리트 멤버를 인스턴스화하는 것을 보여줍니다.
Joshua Clayton

1
당신의 주장은 정말 대단합니다. 매크로 이름이 유효한 식별자가 아니기 때문에 먼저 #VALUE를 정의 할 수 없습니다. 그리고 당신이 할 수 있더라도-누가 그렇게할까요? 헤더 파일은 선언 용입니다-? C'mon .. 헤더에 값을 넣지 말아야하는 유일한 경우는 odr-used와 싸우는 것입니다. 헤더에 값을 넣으면 값을 변경해야 할 때마다 불필요한 재 컴파일이 발생할 수 있습니다.
Aleksander Fular 2016 년

20

Microsoft 컴파일러 [1]를 사용하면 int유사 하지 않은 정적 변수를 Microsoft specific을 사용하여 헤더 파일에서 정의 할 수 있지만 클래스 선언 외부에서 정의 할 수도 있습니다 __declspec(selectany).

class A
{
    static B b;
}

__declspec(selectany) A::b;

나는 이것이 좋다고 말하는 것이 아니라, 그냥 할 수 있다고 말합니다.

[1] 요즘에는 MSC보다 더 많은 컴파일러 ( __declspec(selectany)적어도 gcc와 clang)가 있습니다. 아마 더


17
int foo::i = 0; 

변수를 초기화하기위한 올바른 구문이지만 헤더가 아닌 소스 파일 (.cpp)로 이동해야합니다.

변수는 정적 변수이므로 컴파일러는 하나의 사본 만 작성하면됩니다. 컴파일러에서 어디에 삽입해야하는지 알려주려면 코드에 "int foo : i"줄이 있어야합니다. 그렇지 않으면 링크 오류가 발생합니다. 그것이 헤더에 있으면 헤더를 포함하는 모든 파일에 사본이 생길 것이므로 링커에서 여러 번 정의 된 기호 오류를 얻습니다.


12

여기에 주석으로 추가 할 충분한 담당자가 없지만 IMO는 어쨌든 #include 가드로 헤더를 작성하는 것이 좋습니다 . 이미 별도의 CPP 파일을 사용하지 않는 한 정적 비 통합 멤버를 초기화하기 위해 파일을 사용할 필요는 없습니다.

#ifndef FOO_H
#define FOO_H
#include "bar.h"

class foo
{
private:
    static bar i;
};

bar foo::i = VALUE;
#endif

이를 위해 별도의 CPP 파일을 사용할 필요가 없습니다. 물론 가능하지만 기술적 인 이유는 없습니다.


21
#include 경비원은 번역 단위당 여러 정의를 방지합니다.
Paul Fultz II

3
좋은 스타일에 관하여 : 당신은 종결 endif에 의견을 추가해야합니다 :#endif // FOO_H
Riga

9
이것은 foo.h를 포함하는 컴파일 유닛이 하나만있는 경우에만 작동합니다. 두 개 이상의 cpps에 일반적인 상황 인 foo.h가 포함되어 있으면 각 cpp는 동일한 정적 변수를 선언하므로 파일과 함께 패키지 컴파일을 사용하지 않는 한 링커는`foo :: i '의 여러 정의에 대해 불평합니다 (컴파일 모든 cpp를 포함하는 하나의 파일). 그러나 패키지 컴파일은 훌륭하지만 문제의 해결책은 cpp에서 (int foo :: i = 0;)을 선언하는 것입니다!
Alejadro Xalabarder

1
또는 그냥 사용하십시오#pragma once
tambre

12

복합 유형 (fe 문자열)을 초기화하려면 다음과 같이 할 수 있습니다.

class SomeClass {
  static std::list<string> _list;

  public:
    static const std::list<string>& getList() {
      struct Initializer {
         Initializer() {
           // Here you may want to put mutex
           _list.push_back("FIRST");
           _list.push_back("SECOND");
           ....
         }
      }
      static Initializer ListInitializationGuard;
      return _list;
    }
};

ListInitializationGuard내부 정적 변수 SomeClass::getList()생성자 번 호출된다는 것을 의미 한번만 구축한다 방법. 이 의지 initialize _list당신이 필요로하는 값으로 변수입니다. 이후의 호출 getList은 이미 초기화 된 _list객체 를 반환 합니다.

물론 메소드 _list를 호출하여 항상 객체 에 액세스해야합니다 getList().


1
여기에 구성원 개체 당 만드는 하나 개의 방법이 필요하지 않습니다이 관용구의 버전입니다 : stackoverflow.com/a/48337288/895245이
치로 틸리郝海东冠状病六四事件法轮功

9

여러 객체에 작동하는 C ++ 11 정적 생성자 패턴

하나의 관용구가 https://stackoverflow.com/a/27088552/895245 에서 제안 되었지만 멤버별로 새로운 방법을 만들 필요가없는 더 깨끗한 버전이 있습니다.

main.cpp

#include <cassert>
#include <vector>

// Normally on the .hpp file.
class MyClass {
public:
    static std::vector<int> v, v2;
    static struct StaticConstructor {
        StaticConstructor() {
            v.push_back(1);
            v.push_back(2);
            v2.push_back(3);
            v2.push_back(4);
        }
    } _staticConstructor;
};

// Normally on the .cpp file.
std::vector<int> MyClass::v;
std::vector<int> MyClass::v2;
// Must come after every static member.
MyClass::StaticConstructor MyClass::_staticConstructor;

int main() {
    assert(MyClass::v[0] == 1);
    assert(MyClass::v[1] == 2);
    assert(MyClass::v2[0] == 3);
    assert(MyClass::v2[1] == 4);
}

GitHub의 상류 .

컴파일하고 실행하십시오.

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

참조 : C ++ 정적 생성자는? 개인 정적 객체를 초기화해야합니다

우분투에서 테스트 19.04.

C ++ 17 인라인 변수

https://stackoverflow.com/a/45062055/895245에 언급되어 있지만 더 명확하게하기 위해 다중 파일 실행 가능 예제 가 있습니다. 인라인 변수는 어떻게 작동합니까?


5

헤더 가드를 사용하는 경우 헤더 파일에 할당을 포함시킬 수도 있습니다. 이 기술을 내가 만든 C ++ 라이브러리에 사용했습니다. 동일한 결과를 얻는 또 다른 방법은 정적 메서드를 사용하는 것입니다. 예를 들어 ...

class Foo
   {
   public:
     int GetMyStatic() const
     {
       return *MyStatic();
     }

   private:
     static int* MyStatic()
     {
       static int mStatic = 0;
       return &mStatic;
     }
   }

위 코드에는 CPP / 소스 파일이 필요없는 "보너스"가 있습니다. 다시 C ++ 라이브러리에 사용하는 방법입니다.


4

나는 Karl의 아이디어를 따릅니다. 나는 그것을 좋아하고 지금도 사용합니다. 표기법을 약간 변경하고 기능을 추가했습니다.

#include <stdio.h>

class Foo
{
   public:

     int   GetMyStaticValue () const {  return MyStatic();  }
     int & GetMyStaticVar ()         {  return MyStatic();  }
     static bool isMyStatic (int & num) {  return & num == & MyStatic(); }

   private:

      static int & MyStatic ()
      {
         static int mStatic = 7;
         return mStatic;
      }
};

int main (int, char **)
{
   Foo obj;

   printf ("mystatic value %d\n", obj.GetMyStaticValue());
   obj.GetMyStaticVar () = 3;
   printf ("mystatic value %d\n", obj.GetMyStaticValue());

   int valMyS = obj.GetMyStaticVar ();
   int & iPtr1 = obj.GetMyStaticVar ();
   int & iPtr2 = valMyS;

   printf ("is my static %d %d\n", Foo::isMyStatic(iPtr1), Foo::isMyStatic(iPtr2));
}

이 출력

mystatic value 7
mystatic value 3
is my static 1 0

3

privateStatic.cpp 파일에서도 작업 :

#include <iostream>

using namespace std;

class A
{
private:
  static int v;
};

int A::v = 10; // possible initializing

int main()
{
A a;
//cout << A::v << endl; // no access because of private scope
return 0;
}

// g++ privateStatic.cpp -o privateStatic && ./privateStatic

3

약 어떤 set_default()방법?

class foo
{
    public:
        static void set_default(int);
    private:
        static int i;
};

void foo::set_default(int x) {
    i = x;
}

set_default(int x)메소드 만 사용하면 static변수가 초기화됩니다.

이것은 나머지 의견과 일치하지 않을 것입니다. 실제로 전역 범위에서 변수를 초기화하는 것과 동일한 원칙을 따르지만이 방법을 사용하면 정의하는 대신 명시적이고 이해하기 쉽습니다. 거기에 걸려있는 변수의.


3

발생한 링커 문제는 다음과 같은 원인 일 수 있습니다.

  • 헤더 파일에 클래스 및 정적 멤버 정의를 모두 제공
  • 이 헤더를 둘 이상의 소스 파일에 포함합니다.

이것은 C ++로 시작하는 사람들에게 일반적인 문제입니다. 정적 클래스 멤버는 단일 변환 단위, 즉 단일 소스 파일에서 초기화되어야합니다.

불행히도 정적 클래스 멤버는 클래스 본문 외부에서 초기화해야합니다. 이것은 헤더 전용 코드 작성을 복잡하게하므로 상당히 다른 접근법을 사용하고 있습니다. 정적 또는 비 정적 클래스 함수를 통해 정적 객체를 제공 할 수 있습니다. 예를 들면 다음과 같습니다.

class Foo
{
    // int& getObjectInstance() const {
    static int& getObjectInstance() {
        static int object;
        return object;
    }

    void func() {
        int &object = getValueInstance();
        object += 5;
    }
};

1
나는 C ++이 진행되는 한 여전히 완전한 n00b이지만, 이것은 나에게 훌륭해 보입니다. 감사합니다! 싱글 톤 객체의 수명주기를 무료로 완벽하게 관리 할 수 ​​있습니다.
Rafael Kitover

2

상수를 정의하는 "구식"방법 중 하나는 상수를 다음과 같이 바꾸는 것입니다 enum.

class foo
{
    private:
        enum {i = 0}; // default type = int
        enum: int64_t {HUGE = 1000000000000}; // may specify another type
};

이 방법을 사용하면 정의를 제공 할 필요가 없으며 상수 lvalue를 만들지 않아도되므로 실수로 ODR을 사용 하는 등의 두통을 줄일 수 있습니다.


1

나는 이것을 처음 만났을 때 조금 이상한 것을 언급하고 싶었다.

템플릿 클래스에서 개인 정적 데이터 멤버를 초기화해야했습니다.

.h 또는 .hpp에서 템플릿 클래스의 정적 데이터 멤버를 초기화하는 방법은 다음과 같습니다.

template<typename T>
Type ClassName<T>::dataMemberName = initialValue;

0

이것이 당신의 목적에 도움이됩니까?

//header file

struct MyStruct {
public:
    const std::unordered_map<std::string, uint32_t> str_to_int{
        { "a", 1 },
        { "b", 2 },
        ...
        { "z", 26 }
    };
    const std::unordered_map<int , std::string> int_to_str{
        { 1, "a" },
        { 2, "b" },
        ...
        { 26, "z" }
    };
    std::string some_string = "justanotherstring";  
    uint32_t some_int = 42;

    static MyStruct & Singleton() {
        static MyStruct instance;
        return instance;
    }
private:
    MyStruct() {};
};

//Usage in cpp file
int main(){
    std::cout<<MyStruct::Singleton().some_string<<std::endl;
    std::cout<<MyStruct::Singleton().some_int<<std::endl;
    return 0;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.