반응형


<헤더파일>

...

class Nice

{

private:

   static int totalObject;

// static int totalObject = 0;  (안 돼!)

public:

    Nice();

    ~Nice();

    void ImAMethod();

}

...

 

<구현파일>

...

int Nice::totalObject = 0; // static 멤버 변수의 초기화

 

Nice::Nice()

{

    // totalObject = 0;  (안 돼!)

...







// 정적 변수

 

Num은 클래스의 멤버이면서 클래스로부터 생성되는 모든 객체가 공유하는 변수여야 한다. 이것이 바로 정적 멤버 변수의 정의이며 이 문제를 풀 수 있는 유일한 해결책이다.

 

//h. 파일

class Count

{

private:

     int Value;

    static int Num;

public:

     Count() { Num++; }

     ~Count() { Num--; }

     void OutNum() {

          printf("현재 객체 개수 = %d\n",Num);

     }

};

 

//cpp 파일

int Count::Num=0;

.....

 

헤더 파일의 클래스 선언부에 정적 멤버 변수에 대한 내부 선언이 있고 구현 파일에 정적 멤버 변수에 대한 외부 정의 및 초기값 지정문이 온다. 이게 관례이다. 만약 헤더 파일에 외부 정의를 둔다면 헤더 파일이 두 번 인클루드될 때 이중 정의되므로 에러로 처리될 것이다.

 

이렇게 선언하면 Num은 Count 클래스에 소속되며 외부 정의에서 지정한 초기값으로 딱 한 번만 초기화된다. Count형의 객체 A,B,C가 생성되었다면 각 객체는 자신의 고유한 멤버 Value를 개별적으로 가지며 정적 멤버 변수 Num은 모든 객체가 공유한다. 그래서 각 객체의 생성자에서 증가, 파괴자에서 감소하는 대상은 공유된 변수 Num이며 한 변수값을 모든 객체가 같이 관리하므로 Num은 생성된 객체의 정확한 개수를 유지할 수 있다.

 

정적 멤버 변수는 객체와 논리적으로 연결되어 있지만 객체 내부에 있지는 않다. 정적 멤버 변수를 소유하는 주체는 객체가 아니라 클래스이다. 그래서 객체 크기에 정적 멤버의 크기는 포함되지 않으며 sizeof(C) = sizeof(Count)는 객체의 고유 멤버 Value의 크기값인 4가 된다.

 

단 외부에서 정적 멤버 변수를 정의할 때는 예외적으로 액세스 속성에 상관없이 초기값을 줄 수 있다. 초기식은 대입과는 다르므로 액세스 속성의 영향을 받지 않는다. 정적 멤버 변수를 외부에서도 참조할 수 있도록 공개하려면 클래스 선언부의 public영역에 선언하면 된다. 외부에서 정적 멤버를 액세스할 때는 반드시 소속을 밝혀야 하는데 두 가지 방법으로 소속을 밝힐 수 있다.

 

Count C;

Count::Num=3;            // 클래스 소속

C.Num++;                        // 객체 소속

 

원한다면 C.Num처럼 객체.멤버 식으로 객체의 소속인 것처럼 표현할 수도 있다. 이 때 C객체의 이름은 별다른 의미는 없으며 C객체가 소속된 클래스를 밝히는 역할만 한다. 정적 멤버에 대해 객체의 소속으로 액세스하는 것은 일단 가능하지만 일반적이지 않으며 바람직하지도 않다. 정적 멤버는 논리적으로 클래스 소속이므로 가급적이면 클래스::멤버 식으로 액세스하는 것이 합당하다.

 

//정적 함수

 

정적 멤버 함수의 개념도 정적 멤버 변수의 경우와 비슷하다. 객체와 직접적으로 연관된다기보다는 클래스와 연관되며 생성된 객체가 하나도 없더라도 클래스의 이름만으로 호출할 수 있다. 일반 멤버 함수는 객체를 먼저 생성한 후 obj.func() 형식으로 호출한 객체에 대해 어떤 작업을 한다. 이에 비해 정적 멤버 함수는 Class::func() 형식으로 호출하며 클래스 전체에 대한 전반적인 작업을 한다. 주로 정적 멤버 변수를 조작하거나 이 클래스에 속한 모든 객체를 위한 어떤 처리를 한다.

정적 멤버 함수를 선언하는 방법은 정적 멤버 변수와 동일하다. 클래스 선언부의 함수 원형앞에 static이라는 키워드만 붙이면 된다. 정적 멤버 함수의 본체는 클래스 선언부에 인라인 형식으로 작성할 수도 있고 아니면 외부에 따로 정의할 수도 있는데 외부에 작성할 때 static 키워드는 생략한다.

 

#include <Turboc.h>

 

class Count

{

private:

     int Value;

     static int Num;

 

public:

     Count() { Num++; }

     ~Count() { Num--; }

     static void InitNum() {

          Num=0;

     }

     static void OutNum() {

          printf("현재 객체 개수 = %d\n",Num);

     }

};

int Count::Num;

 

void main()

{

     Count::InitNum();

     Count::OutNum();

     Count C,*pC;

     C.OutNum();

     pC=new Count;

     pC->OutNum();

     delete pC;

     pC->OutNum();

     printf("%d",sizeof(C));

}

 

InitNum은 정적 멤버 함수이므로 Count 클래스의 객체가 전혀 없는 상태에서도 호출될 수 있다. main에서 Count::InitNum()을 먼저 호출하여 Num을 0으로 초기화하였다. 변수를 초기화하는 별도의 함수를 만들었으므로 원한다면 실행중에 언제든지 이 함수를 호출하여 Num을 0으로 리셋할 수도 있다.

 

C 객체를 생성한 후 C.OutNum()을 호출하면 1이 출력되고 pC객체를 동적 생성한 후 pC->OutNum()을 호출하면 2가 출력된다. 이 두 호출의 예처럼 정적 멤버 함수를 객체의 이름으로 호출할 수도 있지만 이때 객체의 이름은 아무런 의미가 없으며 컴파일러는 객체가 소속된 클래스의 정보만 사용한다. C.OutNum(), pC->OutNum(); 이라는 표현을 허용할 뿐이지 이 호출은 실제로 Count::OutNum()으로 컴파일된다는 얘기다.

 

그래서 delete pC;로 pC 객체를 해제한 후에도 pC->OutNum()이라는 호출이 정상적으로 동작한다. 컴파일러는 pC에 실제로 객체가 생성되어 있는지를 볼 필요도 없으며 pC가 Count *형이라는 것만 참조할 뿐이다. 심지어 main의 4번째 줄에 pC가 할당되기도 전인 C.OutNum()을 pC->OutNum()으로 바꿔도 잘 동작한다. 이걸 보면 컴파일러가 포인터의 타입만으로 호출할 함수를 결정한다는 것을 알 수 있다.

 

주의)

정적 멤버 함수는 특정한 객체에 의해 호출되는 것이 아니므로 숨겨진 인수 this가 전달되지 않는다. 클래스에 대한 작업을 하기 때문에 어떤 객체가 자신을 호출했는지 구분할 필요가 없으며 따라서 호출한 객체에 대한 정보도 필요가 없는 것이다. 그래서 정적 멤버 함수는 정적 멤버만 액세스할 수 있으며 일반 멤버(비정적 멤버)는 참조할 수 없다. 왜냐하면 일반 멤버 앞에는 암시적으로 this->가 붙는데 정적 멤버 함수는 this를 전달받지 않기 때문이다. 정적 멤버 함수인 InitNum에서 비정적 멤버인 Value를 참조하는 것은 불가능하다.

 

static void InitNum() {

     Num=0;

     Value=5;

}

 

이 코드를 컴파일하면 정적 멤버 함수에서 Value를 불법으로 참조했다는 에러 메시지가 출력된다. InitNum의 본체에서 Value를 칭하면 누구의 Value인지를 판단할 수 없다. 또한 정적 멤버 함수는 생성된 객체가 전혀 없어도 호출할 수 있는데 이때 Value는 아예 존재하지도 않는다. 비정적 멤버 함수도 호출할 수 없으며 오로지 정적 멤버만 참조할 수 있다.

[출처] Static 멤버 변수의 초기화|작성자 

반응형

+ Recent posts