Static 정적 맴버 변수/멤버 함수


서론

이 장은 C++ 클래스내의 Static 맴버 변수에 특성에 대해 설명하고 선언과 정의에 대해 구분한다.

목차

  1. Static 멤버 변수
  2. 선언과 정의의 구분
    1. 선언(Declaration)
    2. 정의
  3. 정의 및 초기화는 헤더 파일 내에서는 불가능하다
  4. Static const 멤버 변수
  5. Private인 static 멤버 변수 초기화
  6. Static 멤버 함수
  7. Static 멤버 함수를 사용하는 이유
  8. Static 멤버 함수는 this포인터를 사용할 수 없다.
  9. 멤버 함수 포인터
    1. 이유

Static 멤버 변수

모든 객체가 공유하는 멤버 변수이기 때문에 메모리는 프로그램 시작때부터 차지하고 있으며 메모리에 내내 값이 유지된다.

  • 객체 생성 전에도 메모리 존재
  • 객체와는 독립적으로 존재

객체 이름으로도 접근이 가능하지만 클래스 이름으로도 접근이 가능하다.

int Something::s_value = 1;

선언과 정의의 구분

선언(Declaration)

컴파일러에게 변수의 정보만을 주며 실제 메모리사용하지 않는다.

아래 코드는 static멤버 변수 m_value의 존재컴파일러에게 알려주는 것일 뿐이다.

class Something
{
public:
  static int m_value;  // 선언. 메모리를 차지 하지 않는다.
}

정의

실제 메모리가 할당되며 초기화 하는 것도 정의해주는 과정이다.

아래 코드는 static멤버 변수 m_value의 메모리를 할당해주고 값을 1을 넣는 과정이다.

int Something::m_value = 1;  // 정의. 전역 범위의 메모리를 할당 받는다.

static 멤버 변수는 모든 객체가 공유해야 하므로 프로그램 전체 영역에서 메모리가 유지되야 한다.

반드시 전역 범위에서 정의 및 초기화를 해줘야 한다.

따라서 static 멤버 변수는 main함수는 물론이고 생성자 안에서도 초기화 할 수 없다.

#include <iostream>

using namespace std;

class Something
{
public:
	static int m_value; // 선언. .
};

int Something::m_value = 1; // 👈👈 전역 범위에서 초기화(정의)

int main()
{
    // int Something::m_value = 1;  👈👈 에러!!! 전역 범위에서만 가능. 

    cout << Something::m_value << endl;  // 1 출력 👈👈 객체 생성 전에도 사용 가능
    cout << &Something::m_value << endl;  // 0x601070 출력 👈👈 객체 생성 전에도 사용 가능

    Something st1;
    Something st2;

    st1.m_value = 2;

    cout << st1.m_value << endl;  // 2 출력 
    cout << &st1.m_value << endl; // 0x601070 출력

    cout << st2.m_value << endl;  // 2 출력
    cout << &st2.m_value << endl; // 0x601070 출력

    st2.m_value = 1024;

    cout << Something::m_value << endl; // 1024 출력

    return 0;
}

정의 및 초기화는 헤더 파일 내에서는 불가능하다

헤더 파일과 .cpp파일로 분리할 때, static멤버 변수는 반드시 .cpp파일에서 초기화 한다.

  • static 멤버 변수의 초기화는 헤더파일에선 불가능하다.
    • 여러 곳에서 헤더 파일을 include할 때 마다 static 멤버 변수를 여러번 정의 및 초기화 하는게 됨으로.

📜 Something.h

class Something
{
public:
	static int m_value; // 클래스 내에선 초기화 불가능
};

int Something::m_value = 1; // 💥에러!💥 헤더 파일 내에선 초기화 불가능

📜 Something.cpp

int Something::m_value = 1;

Static const 멤버 변수

  • static const멤버 변수는 클래스 내에서 초기화 하는 것이 가능하다.
static const int m_value;
  • 모든 객체가 공유하고 const 특성상 초기화 이후에 값을 변경하는 것이 불가능
  • 클래스 내부에서 초기화가 가능하다.
  • 헤더파일 내에서도 초기화가 가능하다.
#include <iostream>

using namespace std;

class Something
{
public:
	static const int m_value; // 가능! 🙆‍♀️
};

// int Something::m_value = 1;  👈 const라 기존 값 변경이 불가능

int main()
{
    Something st;

    st.m_value = 1024;  // 👈 💥에러💥 const라서 값 변경 불가

    return 0;
}

Private인 static 멤버 변수 초기화

static 멤버 변수는 클래스 내부에선 초기화가 불가능하므로 private하더라도 클래스 외부에서 정의가 가능하다.

#include <iostream>

using namespace std;

class A
{
private:  // ⭐⭐⭐
	static int s_value;
};

int A::s_value = 1;    // 👈 private이더라도 전역범위에서 정의 및 초기화 가능

int main()
{
	A a;
	cout << a.s_value << endl;

	A::s_value = 1;  // 👈 💥에러!💥 private이므로 s_value 에접근이 불가능하다.

    return 0;
}

Static 멤버 함수

  1. 객체독립적이고 객체 생성상관없다.
  2. 따라서 멤버 변수객체생성되야 메모리할당받기 때문에 static 멤버함수 내에서는 멤버 변수사용할 수 없다.
  3. 미리 전역에서 메모리가 할당되는 static멤버 변수사용이 가능하다.
class Something
{
private:
    int noramal_value = 99;  // 일반 멤버 변수
    static int static_value; // static 멤버 변수

public:
    static void Func()     // ⭐ static 멤버 함수
    {
        int a = 1024;      // Func()내의 일반 지역 변수 
        cout << a << endl;

        // cout << noramal_value << endl; 👈👈 💥에러!!!💥 일반 멤버 변수는 사용할 수 없다. 

        cout << static_value << endl; // static 멤버 변수는 사용 가능
    }
};

int Something::static_value = 777;

int main()
{
    Something::Func();  // 객체 생성 없이 바로 클래스 이름으로 호출 가능

    /* 👆 1024와 777을 출력한다. */

    return 0;
}

Static 멤버 함수를 사용하는 이유

  • 객체 생성 여부상관없이 클래스 이름으로 접근하고 싶을 때 사용
  • private한 static멤버 변수접근하려 할 때 많이 사용
  • static멤버 변수모든 객체들이 사용하고 공유해야 하는데 private외부에서 직접 접근하고 사용할 수 없다.
  • priavte 멤버 변수들은 멤버 함수들에서만 접근이 가능하다는 특징이 있다.
  • static 멤버 함수를 통해서 private한 static멤버 변수간접 접근할 수 있다.(getter, setter 접근함수 처럼)
class Something
{
private:
    static int static_value; // private한 static 멤버 변수

public:
    static int getValue()     // ⭐ static 멤버 함수
    {
        return static_value;  
    }
};

int Something::static_value = 777;

int main()
{
    // cout << Something::static_value << endl;  👈👈 💥에러!💥 private이므로 직접 접근 불가능

    cout << Something::getValue() << endl;   // 777 출력. 클래스 이름으로 호출했을 때. 

    Something s;
    cout << s.getValue() << endl;  // 777 출력. 객체 이름으로 호출했을 때. 

    return 0;
}

이처럼 private한 staitc 멤버 변수를 직접 접근할 수는 없지만 static 멤버 함수 getValue() 를 통하여 간접적으로 staitc 멤버 변수를 리턴받을 수 있다.

Static 멤버 함수는 this포인터를 사용할 수 없다.

  • this 포인터는 객체인 자기 자신의 주소를 담고 있기 때문에 객체생성되야 사용가능하다.
  • static 멤버 함수객체들의 생성무관하며 언제든 클래스 이름으로 접근 가능해야 하기 때문에 static멤버 함수 내부에서는 this포인터를 사용할 수 없다.
class Something
{
private:
    static int static_value; // static 멤버 변수

public:
    static int getValue()     // ⭐ static 멤버 함수
    {
        return this -> static_value;  // 💥에러💥 
    }

    int temp()  // ⭐ 일반 멤버 함수
    {
        return this -> static_value; // 문제 없다.
    }
};
  • static 멤버 함수인 getValue()에선 this를 사용할 수 없다.
  • 일반 멤버 함수인 temp()에선 this를 사용할 수 있다.

멤버 함수 포인터

일반 함수는 함수 이름에 함수의 주소값이 들어가 있다. 그러나 멤버 함수의 포인터는 조금 다르다.

멤버 함수의 포인터&클래스이름 :: 함수이름 으로 접근해야 한다.

class Something
{
public:
    int temp() { return 1; }
};

int main()
{
    Something s1;
    Something s2;

    int (Something::*fptr_1)() = s1.temp;  // 💥에러💥
    int (Something::*fptr_2)() = &Something::temp;  // 문제 없다.
}
  • int (Something::*fptr_2)() = &Something::temp;
    • 멤버 함수의 포인터는 이렇게 & 클래스이름 :: 함수이름으로 으로 접근해야 한다.

이유

  • 멤버 변수는 각 객체마다 따로 메모리를 가져 주소가 다르다
  • 멤버 함수객체마다 함수 메모리따로 갖는 방식아니다
    • 멤버 함수는 어딘가 한 군데 저장돼 있고 각 객체마다 그 공간에 동일하게 접근하여 각자의 다른 데이터로 사용하는 방식이다.
  • 따라서 일반 함수의 주소와는 다르게 멤버 함수의 주소를 받아와야 한다.
    • 그럴려면 속해있는 클래스가 어디인지 알려주어야 한다. &Something::

    [reference]

    https://ansohxxn.github.io/cpp/chapter8-11/