참고자료
https://www.yamyamcoding.com/91c0b5c9-d4da-414a-8b24-35ccf8b8475c
게임 프로그래머 취업 비법서(인터뷰 자료)
Notion 팁: 페이지를 생성할 때는 명확한 제목과 관련된 내용이 필요합니다. 인증된 정보를 사용하고, 페이지 주제를 확실히 하고, 주요 이슈에 대한 의견을 공유하세요.
www.yamyamcoding.com
1. 객체 지향과 C++
C++는 객체지향과 절차지향 두개를 모두 지원하는 멀티 패러다임 언어이다,
- 객체지향과 절차적 프로그래밍
- 객체지향: 프로그램 설계 시 프로그램을 수많은 객체로 나누고, 이 객체들의 상호작용을 서술하는 방법
- 절차적 프로그래밍: 프로그램 설계 시 함수를 중점으로 사용하여, 구조 로직을 설계
- 객체지향의 특징 (장점)
- 캡슐화
- 같은 역할을 하는 변수나 함수들을 모아두어서, 의존성, 커플링이 줄어든다.
- 결과적으로 관리가 편해진다.
- 정보 은닉
- private을 사용하여 프로그램의 세부 구현을 감추고, public을 통해 접근하도록 하기
- 의도치 않은 접근을 제한하여, 잘못 쓰는 것을 방지 (제대로 쓰기는 쉽게, 잘못 쓰기는 어렵게 설계하기)
- 상속
- 자식 클래스가 부모 클래스의 변수, 함수를 물려받는 것
- 자식 클래스가 overriding을 통해 더욱 디테일하게 구현하기
- 생산성과 유지보수성을 높일 수 있다.
- 다형성
- 상속에 의한 다형성
- 오버로딩: 함수 이름은 같은데, 인자 타입이나 갯수가 다른 것
- 오버라이딩: 상속 관계에서 부모의 메서드를 자식이 재정의하는 것
- 템플릿에 의한 다형성:
- 템플릿의 타입은 컴파일 타임에 결정되어 인스턴스화를 하기 때문에 컴파일 타임에 컴파일러가 실제 코드 구현부를 모두 볼 수 있어야 한다.
- 템플릿 클래스의 함수 구현은 헤더 파일에 들어가야 한다. (https://gbleem.tistory.com/14 )
- 캡슐화
- 클래스/구조체
- 패딩
- 성능 향상을 위해 추가적인 메모리 할당을 하는 것
- 구조체나, 클래스의 가장 큰 자료형의 크기로 설정된다.
- 예제
- 패딩
struct Point //8byte
{
short point_x; //2 (+2 padding)
int point_y; //4
};
struct Player //20byte -> 가장 큰 자료형이 Point의 int이므로 4바이트 맞춰주기
{
char rank; //1
short hp; //2
//+1 padding
short mp; //2
//+2 padding
Point point;//8
Name name; //2
//+2 padding
};
2. 포인터 변수와 참조자
- 기본 개념
- 참조자: 실체가 있어야 하며, 선언 즉시 할당되어야 한다. (nullptr로 할당 불가능)
- 포인터: 주소값을 저장할 수 있는 타입의 변수, nullptr 가능, 동적 메모리 할당에 사용
- 포인터 타입 변수
- malloc: 할당 시 메모리의 사이즈를 입력해서 할당, 함수이다
- new: 할당시 객체의 크기를 입력하여 할당, 연산자이다
int* c_style = (int*)malloc(sizeof(int)*10);
int* cpp_style = new int[10];
...
free(c_style);
delete cpp_style;
- 댕글링 포인터
- 이미 할당 해제된 변수를 다시 참조하려고 할 때 (segment fault)
- 스마트 포인터로 이런 문제들을 해결할 수 있다.
- 메모리 누수( https://gbleem.tistory.com/12 / https://gbleem.tistory.com/8)
- 스마트 포인터 ( https://gbleem.tistory.com/9 )
- 객체가 더이상 필요하지 않을 때 자동으로 해제
- 종류
- unique_ptr
- 하나의 주소만 가리킬 때 (복사x, 이동o)
- 매개변수로 쓰려면, 레퍼런스로 받기!! (복사 못하니까)
- 임의의 크기의 스마트 포인터 배열 생성 unique_ptr<int[ ]> pArr = make_unique<int [ ]>(10);
- reset()으로 원본 객체 소멸 및 포인터 해제
- release()로 원본 객체 소유권 해제, 원본 객체 리턴
- get()을 통해 unique_ptr 끼리 주소값 비교 가능
- shared_ptr
- 레퍼런스 카운팅 방식을 사용하여 메모리 관리, 카운트가 0이 되면 해제
- reset() 을 인수를 사용해서 호출하기
- 만약 인수 없이 호출하면, 레퍼런스 카운트가 1 감소하고,
- shared_ptr 객체가 아무것도 가리키지 않게 된다.
- use_count()를 통해 레퍼런스 카운트 반환 가능
- weak_ptr
- shared_ptr의 상호참조 문제를 해결
- 레퍼런스 카운트를 올리지 않는 shared_ptr 이라고 생각하면 된다.
- unique_ptr
3. RAII와 동적메모리 자원 관리
- RAII (Resource Acuquisition Is Initialization)
- 의미
- 자원 획득을 객체 초기화 시에만 해라, 자원의 생애주기를 객체의 생애주기에 바인딩하기
- 그러나 자원 획득이 필요한 경우 자원 획득을 하는 클래스를 만들어서 그 클래스의 생성자에서만 자원 획득을 해라
- 동적 메모리 할당, 파일 열기, 락 등의 자원 획득에 있어서, 자원 획득 담당 클래스를 만들자는 것
- 필요성
- RAII 패턴은 자원을 사용하고자 하는 상황에서
- 생성자가 자원 획득을 하고
- 소멸자가 자원 해제를 하는 프로그래밍 패턴
- 객체의 시작과 끝을 컴퓨터가 알아서 해주는 이점을 활용하여, 자원관리를 쉽게 하고자 하는 것
- 결과적으로, 자원의 생애 주기가 객체의 생애 주기와 결합되고, 객체의 생애는 런타임이 알아서 관리를 해주기 때문에 자원 또한 런타임이 자동으로 관리할 수 있게 된다.
- RAII 패턴은 자원을 사용하고자 하는 상황에서
- 의미
4. 가상 함수
- 추상 클래스
- (C++에서)순수 가상 함수가 하나라도 포함된 클래스
- 추상 클래스는 인스턴스화 할 수 없다.
- 순수 가상 함수는 반드시 자식 클래스에서 오버라이드 해서 사용한다.
- virtual
- 객체의 생성 및 소멸과정에서 (생성자와 소멸자에서) 가상함수를 호출하면 안된다.
- 예를 들어
- 자식 객체 생성 시에 가상함수를 호출하면,
- 부모가 먼저 생성될 때 자식 객체는 아직 초기화 되지 않은 상태이므로
- 자식 객체는 자신이 부모 객체인 것처럼 동작한다.(부모 클래스의 virtual이 호출되므로 의도치 않은 동작을 하게 된다.)
- 가상 함수 테이블
- 가상 함수 테이블은 클래스마다 존재한다.
- 각 클래스 인스턴스들은 가상함수 테이블을 가리키는 포인터 변수를 하나씩 가진다.
- 그렇기 때문에 가상함수가 있는 클래스 인스턴스 메모리 크기를 구할 때,
- 포인터 변수의 크기를 더해주는것 잊지 말아야 한다 (32bit 에서 4byte)
class Base //24 byte
{
public:
virtual void Speak();
private:
int data1; //4
int data2; //4
char name; //1 + 7(padding)
//vt 8
};
class Derived : public Base //32 byte
{
public:
void Speak() override;
private:
short data; //2 + 6(padding)
//base 24 (가장 큰 타입은 vt: 8byte)
};
5. C++ 스타일 캐스팅
- const_cast
- 포인터 객체의 상수성을 제거할 때 사용
- 상수성은 항상 유지되어야 하지만, 다른 라이브러리를 사용하는 등 인자값으로 비상수성 객체를 요구하는 등의 불가피한 상황에서 사용
- 단 인자값으로 받는 함수 내에서 해당 인자로 받은 변수를 수정하지 않는 것을 알고있을 때 사용해야 한다.
- static_cast
- 일반적인 캐스팅 방식 (업캐스팅과 다운캐스팅 모두 가능)
- 업캐스팅: 자식 클래스를 부모 클래스로 캐스팅
- 다운캐스팅: 부모 클래스를 자식 클래스로 캐스팅
- 런타임 타입 검사를 하지 않는다는 단점이 있다. (메모리 침범 등의 오류가 발생할 수도 있다.
- 일반적인 캐스팅 방식 (업캐스팅과 다운캐스팅 모두 가능)
- dynamic_cast
- 런타임에 타입 검사를 수행하는 안전한 다운캐스팅에 사용하는 방식
- 런타임 비용이 높아서 잘 안쓴다.
- reinterpret_cast
- 강제 타입 변환 (double을 byte로 변환하는 등)
- 타입을 비트 단위로 1:1 변환시키므로, 원본 데이터 소실 등의 문제가 생길 수 있다.
6. STL 컨테이너
- vector
- list
- map
- 레드 블랙트리로 구성
- 키-값 의 쌍으로 저장하고, 값을 찾을 때 이진 탐색을 사용한다. (sorting되어있음)
- unordered_map
- 해시맵
- 검색이 빠르다. 그러나 공간이 많이 필요해서 충돌이 자주 일어난다.
- 해시 함수에서 역은 성립하지 않는다.
- 해시맵 구현 기법
- 개방 주소법
- 새로운 공간을 만들어서 할당하는 것이 아니라, 원래 있는 공간에 다른 방식으로 삽입하는 방식
- 종류
- 선형 탐색: 충돌된 경우 다음 버킷 혹은 몇개를 건너뛴 버킷에 값을 넣는다.
- 제곱 탐색: 충돌시 제곱만큼 건너뛴 버킷에 데이터 넣는다.
- 이중 해시: 충돌시 다른 해시함수를 한번 더 적용한 결과를 이용한다.
- 체이닝
- 만약 중복된 key가 들어온다면, 연결리스트를 통해 뒤에 계속 넣어주는 방식
- 개방 주소법
- 해시맵
7. 가비지 컬렉터
- C++ 스마트 포인터는 레퍼런스 카운팅 방식 (가비지 컬렉션 아니다)
- C# 혹은 언리얼 엔진은 나이브 마크 앤드 스윕 방식의 가비지 컬렉션 사용
- 나이브 마크 앤드 스윕 (Naive Mark and Sweep)
- 모든 객체의 리스트를 루트 set에서 가지고 있다.
- Mark: 해제할 메모리는 루트 set에서 제외하고, 해제하지 않을 메모리는 가지고 있다.
- Sweep: 연결되지 않은 메모리를 해제한다.
8. C++ 메모리 영역
- 코드 영역
- 실행될 코드를 저장하는 영역
- 데이터 영역
- 정적(static), 전역(global) 변수가 저장되는 공간
- 전역 변수의 경우 프로그램의 시작과 함께 할당되며, 할당 순서가 미정이기 때문에 선언 및 사용시 타이밍에 주의해야한다.
- 힙 영역
- 메모리를 동적으로 할당하고 해제할 수 있는 영역
- 스택 영역
- 클래스의 멤버변수와 지역변수 할당
- 함수의 파라미터, 함수의 반환 주소값 할당
- 함수가 끝나면 하나씩 pop 한다.
9. const 키워드
- 상수 데이터, 비상수 포인터 (const 가 * 왼쪽)
- 가리키는 값을 바꿀 수 없음
- 가리키는 곳을 바꿀 수 있음
- 비상수 데이터, 상수 포인터 (const가 * 오른쪽)
- 가리키는 값을 바꿀 수 있음
- 가리키는 곳을 바꿀 수 없음
- 상수 데이터, 상수 포인터
- 가리키는 값과 가리키는 곳 모두 바꿀 수 없음
'C++' 카테고리의 다른 글
unique_ptr 써보기 (2) | 2025.01.17 |
---|---|
텍스트 RPG 게임 만들기 (C++) (1) | 2025.01.16 |
C++ 디자인 패턴 (1) | 2025.01.06 |
C++ TIL day 13 (1) | 2025.01.03 |
Lambda Expressions (0) | 2025.01.03 |