상세 컨텐츠

본문 제목

C++ 정적 멤버, static

C++/C++98

by deulee 2023. 8. 5. 12:35

본문

정적 멤버 변수

우선 정적 멤버 변수가 무엇인지 설명하도록 하겠다.

class Count
{
	static int cnt; // 정적 멤버 변수
};

정적 멤버 변수는 클래스의 바깥에 선언되어 있지만 클래스에 속하며 객체별로 할당하지 않고 모든 객체가 공유하는 멤버이다. 마치 멤버 함수처럼 말이다.

 

그럼 도대체 이런게 왜 필요한 것일까?

 

다음의 예제를 보도록 하자.

 

#include <iostream>

int cnt = 0;
class Count
{
private:
	int Value;
public:
	Count() {cnt++;};
	~Count() {cnt--;};
	void OutCnt()
	{
		std::cout << "현재 객체의 개수 = " << cnt << std::endl;
	}
};

int main(void)
{
	Count C;
	Count* ptr;
	C.OutCnt();
	ptr = new Count;
	ptr->OutCnt();
	delete ptr;
	C.OutCnt();
    std::cout << sizeof(C) << std::endl; // 크기가 4로 출력될 것이다.
	return 0;	
}

 

우선 위의 예제로 보게 되면 객체가 생성되고 파괴될 때마다 생성자와 파괴자에서 전역 변수인 num의 수를 조절하는 것이 보일 것이다.

 

이렇게 하면 충분히 현재 생성된 객체의 수를 정확히 셀 수 있다. 하지만 이렇게 되면 OOP의 개념이 많이 사라지게 된다.

 

"왜 이렇게 중요한 정보를 클래스 밖에다가 놓아야 하는가?, 클래스만 배포하면 정상적으로 작동하지 않네?, 외부에서 저 전역 변수를 마음대로 바꿀 수 있네?"

 

즉, 이러한 이유로 전역 변수로 객체의 수를 세는 것은 문제가 된다.

 

그럼 반대로 클래스 안에다가 멤버 변수로 선언하면 어떻게 될까?

class Count
{
private:
	int cnt;
};

 

짜잔, 이제 원하는데로 작동할 것 같지만 실은 전혀 아니다.

 

우선 앞서 언급한대로 멤버 변수인 cnt는 객체(인스턴스)별로 메모리가 할당받게 된다. 즉, 아무리 객체를 생성하더라도 생성될 때마다 저 값은 0에서 시작되어 100개의 객체를 만들어도 cnt의 값은 1이 될 것이다. (물론 cnt의 값을 0으로 초기화 했을 때 가정이다.)

 

그래서 맨 처음에 언급되었던 정적 멤버 변수가 필요한 것이다.

 

#include <iostream>

class Count
{
private:
	static int cnt;
	int Value;
public:
	Count() {cnt++;};
	~Count() {cnt--;};
	void OutCnt()
	{
		std::cout << "현재 객체의 개수 = " << this->cnt << std::endl;
	}
};

int Count::cnt = 0;

int main(void)
{
	Count C;
	Count* ptr;
	C.OutCnt();
	ptr = new Count;
	ptr->OutCnt();
	delete ptr;
	C.OutCnt();
	std::cout << sizeof(C) << std::endl; // 크기가 4로 출력된다!!
	return 0;	
}

 

cnt는 여전히 Count 클래스 내부에 선언되어 있지만 static 키워드를 붙여 정적 멤버임을 명시했다.

 

자 그럼 무엇이 달라졌는가.

 

1. 외부에서 별도로 선언 및 초기화를 진행함.

 

cnt는 이 변수가 Count의 멤버라는 것을 알릴 뿐이지 이에 대한 메모리를 할당하지는 않는다. (외부에서 선언으로 생각하기 때문이다.)

 

그래서 별도로 선언 및 초기화를 한것을 볼 수 있을 것이다. 이때 어느 소속의 변수인지 :: 연산자를 통해서 선언해야 하는 것을 주의해야 한다.

 

그래서 sizeof(C) 연산자로 크기를 출력할 때 크기가 4인 것을 확인할 수 있다!!

 

2. 모든 객체가 공유함

 

멤버 변수로 선언되었음에도 모든 객체가 이를 공유하는 것을 확인할 수 있다. 그렇기 때문에 중간 중간 현재 생성된 객체의 수를 확인할 때 정상적으로 출력되는 것을 확인할 수 있다.

 

3. 멤버 변수로서의 은폐성도 지님

 

외부에서 선언되는 것처럼 메모리가 할당된다 하더라도 이 변수는 클래스의 멤버 변수로서 활동한다.

 

이 말이 무엇이냐 하면 외부에서 이 멤버에 대한 접근을 할 수 없다는 것이다. (private으로 지정됨)

 

그렇기 때문에 한층 더 안전해진 모습을 볼 수 있다.

 

물론 초기화는 외부에서 지정할 수 있다.

 

정적 멤버 함수

정적 멤버 함수의 개념도 기본적으로 정적 멤버 변수의 경우와 비슷하게 작동한다.

 

정적 멤버 함수로 정의하게 될 경우 생성된 인스턴스가 하나도 없더라도 클래스의 이름만으로 호출할 수 있다.

 

#include <iostream>

class Count
{
private:
	static int cnt;
	int Value;
public:
	Count() {cnt++;};
	~Count() {cnt--;};
	static void initCnt()
	{
		cnt = 0;
	}
	static void OutCnt()
	{
		std::cout << "현재 객체의 개수 = " << cnt << std::endl;
	}
};

int Count::cnt;

int main(void)
{
	Count::initCnt(); // 인스턴스 없이 실행

	Count C;
	// C.OutCnt();

	Count::OutCnt(); // 인스턴스 없이 호출

	Count* ptr;
	ptr->OutCnt(); // 생성 전에도 호출 가능

	ptr = new Count;
	ptr->OutCnt(); // 생성 후에도 호출 가능

	delete ptr;
	ptr->OutCnt(); // 파괴 후에도 호출 가능
	
	std::cout << sizeof(C) << std::endl;
	return 0;	
}

 

위의 예제를 보면 Count::initCnt()와 Count::OutCnt() 함수는 개별 객체에 대한 함수가 아니라 클래스에 관련된 함수이기 때문에 객체가 생성되지 않더라도 해당 함수를 호출할 수 있다.

 

또한, 동적 할당으로 메모리에 생성되지 않더라도 포인터를 통해 해당 함수를 호출할 수 있고 심지어 파괴된 후에도 호출이 가능한 것을 볼 수 있다.

 

주의할 점

 

하지만 주의할 점이 있다.

 

바로 정적 멤버 함수는 특정한 객체에 의해 호출되는 것이 아니다 보니 컴파일러에 의해 암시적으로 전달되는 인수 this가 전달되지 않는다는 것이다.

 

객체에 대한 작업이 아니라 클래스에 대한 작업을 하는 것이기 때문에 어떤 객체가 자신을 호출했는지 구분할 필요가 없는 것이다.

 

그래서 정적 멤버 함수정적 멤버만 액세스할 수 있으며 일반 멤버(비정적 멤버)는 참조할 수 없다.

 

출처

http://www.soen.kr/lecture/ccpp/cpplec.htm

 

C/C++ 강좌

 

www.soen.kr

 

'C++ > C++98' 카테고리의 다른 글

C++ 연산자 함수  (0) 2023.08.05
C++ 상수 멤버, const  (0) 2023.08.05
C++ this  (0) 2023.08.05
C++ 프렌드  (0) 2023.08.05
C++ 생성자, 파괴자, 복사 생성자, 복사 대입 연산자  (0) 2023.08.03

관련글 더보기