Class

Date:     Updated:

카테고리:

태그:

모두의 코드 씹어먹는 c++ 자료를 보고 정리한 내용입니다.


Class

class Knight
{ 
public:
	void Move(int y, int x);
	void Attack();
	void Die();

public:
	int hp;
	int attack;
	int posY;
	int posX;
};

void Knight::Move(int y, int x)
{
    posY = y;
    posX = x;
}

int main()
{
    Knight k1;
    k1.Move(2, 2);

    return 0;
}
  • Knight 클래스의 객체는 16(4 * 4) 바이트 크기
    • ==멤버 함수는 메모리에 잡히지 않음==
  • k1.Move(2, 2)를 실행할 때 k1의 Position 주소를 어떻게 알지?
    → 전역 함수의 경우 Move(&k1, 2, 2)처럼 k1의 주소를 스택 메모리에 push 함
    → 멤버 함수의 경우 k1 객체의 주소를 레지스터가 들고 있음

class2

→ this라는 포인터 값을 받아오고, offset을 통해 PosY 주소 계산

class1

C++ 오버로딩 규칙

[1] 자신과 타입이 정확히 일치하는 함수를 찾는다.

[2] 정확히 일치하는 타입이 없을 경우 아래와 같은 형변환을 통해 일치하는 함수를 찾는다.

  • char, unsigned char, shortint
  • unsigned shortint or unsigned int
  • floatdouble
  • Enumint

[3] 위와 같이 변환해도 일치하는 것이 없다면 좀 더 포괄적인 형변환을 통해 일치하는 함수를 찾는다.

  • 임의의 숫자(numeric) 타입은 다른 숫자 타입으로 변환한다.
    • ex) flaotint
  • Enum도 임의의 숫자 타입으로 변환된다.
    • ex) Enumdouble
  • 0 은 포인터 타입이나 숫자 타입으로 변환된다.
    • ex) 0char* or 0float
  • 포인터는 void 포인터로 변환된다.

[4] 유저 정의된 타입 변환으로 일치하는 것을 찾는다.

  • ex) 클래스 사이의 명시적 형변환

만약 컴파일러가 위 과정을 통하더라도 일치하는 함수를 찾을 수 없거나 같은 단계에서 두 개 이상이 일치하는 경우 ambiguous(모호) 판단 → 오류 발생

Default Constructor

디폴트 생성자는 인자를 하나도 가지지 않는 생성자이다. 클래스에서 사용자가 어떠한 생성자도 명시적으로 정의하지 않았을 경우 컴파일러가 자동으로 추가해준다.

class Test {
 public:
  Test() = default;  // 디폴트 생성자를 정의해라
};
  • 컴파일러가 만들어 준 디폴트 생성자를 사용하고 싶은 경우 명시적으로 표시할 수 있다.
Test test1 = Test(); // OK
Test test2;          // OK

Test test3();        // X
  • Test test3(); 는 Test를 리턴하는 함수 test3를 정의한 것으로 인식한다.
  • 디폴트 생성자를 호출하기 위해서는 () 붙이면 안됨

Destructor

생성자 : 객체가 생성될 때 자동으로 호출되는 함수. 소멸자 : 객체가 소멸될 때 자동으로 호출되는 함수.

  • 주로 객체가 동적으로 할당받은 메모리를 해제
  • 쓰레드 사이에서 lock을 푸는 것도 중요한 역할

Copy Constructor

T(const T& a);

디폴트 생성자/소멸자 처럼 C++ 컴파일러는 디폴트 복사 생성자를 지원한다. 단, 멤버 변수로 참조나 포인터가 들어있다면 주소 값이 복사되어 동일한 객체를 가리키게 된다. 이런 경우 객체를 소멸할 때 다음과 같은 오류가 발생한다.

   
copy1 copy2
Test test1;
Test test2;

Test test3 = test2; // 복사 생성자 실행

C++ 컴파일러는 생성시의 대입 연산을 복사 생성자로 인식

Static

C++에서는 마치 전역 변수 같지만 클래스 하나에만 종속되는 static 변수가 존재한다.

  • 클래스의 모든 객체들이 ‘공유’하는 변수
  • Stack Memory에 쌓이지 않고 .data / .bss에 쌓임 (초기화 여부 차이)
    • (메모리 찍어보면 객체 저- 멀리에 따로 존재)
    • 프로그램 시작할 때부터 종료될 때까지 항상 올라가 있음
class Marine {
	static int total_marine_num;
};

int Marine::total_marine_num = 5;
class Marine {
	const static int total_marine_num = 5;
};

일반적으로 클래스 내부에서 static 변수를 초기화 할 수 없지만, const static 변수인 경우 가능하다.

Initializer list

초기화 리스트를 사용한 경우 생성과 초기화를 동시에 하게 된다. 일반 변수의 경우 생성자 내에서의 초기화와 초기화 리스트는 차이가 없다. 초기화 리스트가 필요한 경우는 생성과 동시에 초기화가 필요한 참조나 const 타입의 변수가 있고, 멤버 타입이 클래스인 경우 중요하게 사용된다.

class Knight : public Player
{
public:
    Knight() : Player(1)
        // ... 
        // 선처리 영역 
		// Inventory()
        // ...
    {
        _stamina = 100;
        _inventory = Inventory(20);
    }

    void VMove() { cout << "knight move" << endl; }
           
public:
    int _stamina;
    Inventory _inventory;
};
  • Knight가 가지고 있는 Inventory 객체는
    • → 선처리 영역에서 기본 생성자로 한 번 생성되고
    • → 생성자 내에서 또 한 번 생성된다
  • 초기화 리스트로 적절한 생성자를 호출함으로서 선처리 영역의 뻘짓거리를 막을 수 있음

이후 별도 챕터로 다룸

Explicit & Mutable

[1] ==Explicit : 원하지 않는 암시적 변환을 할 수 없도록 컴파일러에게 명시==

class MyString {
  char* string_content;  // 문자열 데이터를 가리키는 포인터
  int string_length;     // 문자열 길이

  int memory_capacity;

 public:
  // capacity 만큼 미리 할당함. (explicit 키워드에 주목)
  explicit MyString(int capacity);

  // 문자열로 부터 생성
  MyString(const char* str);

  // 복사 생성자
  MyString(const MyString& str);

  ~MyString();

  int length() const;
  int capacity() const;
};

// .. (생략) ..

void DoSomethingWithString(MyString s) {
  // Do something...
}

int main() {
  DoSomethingWithString(3);  // Error
}

explicit 키워드가 없었다면, DoSomethingWithString(3) 는 DoSomethingWithString(MyString(3)) 으로 사용자가 의도하지 않은 암시적 변환이 일어나어 실행될 것이다.

[2] Mutable : const 함수 내부에서 값을 수정할 수 있음

class Server {
  // .... (생략) ....

  Cache cache; // 캐쉬!

  // 이 함수는 데이터베이스에서 user_id 에 해당하는 
  // 유저 정보를 읽어서 반환한다.
  User GetUserInfo(const int user_id) const {
    // 1. 캐쉬에서 user_id 를 검색
    Data user_data = cache.find(user_id);

    // 2. 하지만 캐쉬에 데이터가 없다면 데이터베이스에 요청
    if (!user_data) {
      user_data = Database.find(user_id);

      // 그 후 캐쉬에 user_data 등록
      cache.update(user_id, user_data); // <-- 불가능
    }

    // 3. 리턴된 정보로 User 객체 생성
    return User(user_data);
  }
};

mutable 필요한 이유? 멤버 함수를 const 로 선언하는 의미는 [이 함수는 객체의 내부 상태에 영향을 주지 않습니다] 라는 의미이다. 몇몇 예외적인 경우, 내부 상태에 영향을 주지 않는 함수의 의미를 갖지만 값의 변경이 필요한 경우가 존재한다. 이런 경우 다음과 같이 mutable 키워드를 사용하게 된다.

mutable Cache cache;



맨 위로 이동하기

Cpp 카테고리 내 다른 글 보러가기

댓글 남기기