자동으로 garbage collection 지원해주기도 함. (C#, Python, Java)
◆ Nominal Typing vs Structural Typing
타입을 비교하는 방식에 따라 명목적 타이핑, 구조적 타이핑으로 나눌 수 있다.
Nominal Typing(명목적 타이핑)은 말그대로 타입을 비교할 때, 타입의 이름을 기준으로 판단하는 것.
C#이나 C++, Java에서 이걸 쓴다. 그만큼 우리가 잘 알고있는 방식임. 클래스 이름 기준으로 타입 비교하는 것
Structural Typing(구조적 타이핑) 이라는 것도 있다.
타입을 비교할 때 타입의 이름이 아닌 해당 타입에 포함된 멤버(예: 속성이나 메서드)를 기준으로 판단하는 것임.
Go랭이나 타입스크립트에서 이걸 씀.
구조적 타이핑에서는 다음 코드와 같은 방식이 가능하다.
interface Person {
name: string;
age: number;
}
interface Employee {
name: string;
age: number;
}
const person: Person = { name: "Alice", age: 25 };
const employee: Employee = person; // 구조적 타이핑 덕분에 호환 가능
◆ 왜 하필 C++이 게임에서 젤 많이 쓰일까?
C++은 Deterministic 하기 때문.
이 프로그램이 어떻게 실행될 지 "정해져 있다"
가장 명확하게는 메모리 해제를 어떻게 하는지와 관련된 내용
C++는 스테이지1 보스 나중에 더 안쓸거면, 명시적으로 메모리를 해제하면 됨.
얘 다 썼으니까 메모리 죽여~ 이렇게.
반면 Python, 자바, C#은 명시적으로 메모리를 죽이지 않음
걍 어느 순간에 자동으로, 어? 이 메모리 더이상 접근 안하네? 그럼 내가 지워야지~ 함
그런데 이 가비지콜렉터가 언제 작동하는지는 우리가 알 수 없고, 일정하지도 않음. 그래서 디버그가 힘듦
→ 게임프로그래밍에선 완전히 컨트롤 가능한C++를 더 선호!
C++은 속도도 빠르고, 메모리, 스레드, 스토리지.. 등을 직접 관리할 수 있고, 시장 규모도 커서, 문제 해결할 때 굿
(물론 Unity C#도 많이 많이 쓰인다~~..)
◆ OOP
객체지향 프로그래밍과 관련된 내용을 C++ 기반으로 훑어볼 것임
▶ Encapsulation (캡슐화)
불필요한 것들을 알 필요없게 숨겨서 보호하는 것
class 내 멤버들 관리할 때, private/protected/public 접근제한자 사용함.
public 외부에서 접근 가능
protected 상속 받은 애들끼리만 접근 가능
private 내부에서만 사용 (외부엔 숨김)
이런 거 조정해서 Encapsulation 구현
▶ Inheritance (상속)
상속은 "is-a" 관계
circle은 shape의 한 종류이다.
circle is a type of shape
▷ Multiple Inheritance (다중 상속)
여러 부모를 동시에 상속받는 것
상속 구현시 주의해야 할 부분임.
어떤 언어들은 한 클래스가 여러 개의 부모를 가지는걸지원하기도 함
다중 상속은 좋은 점도 있지만, 알 수 없는 문제들이 막 발생할 것임
트리 구조가 아니라 그래프 구조가 되기 때문.
▷ Diamond problem
다중 상속에서 일어날 수 있는 문제점
다중 상속 시 두 부모 클래스가 공통된 조상 클래스를 가지고 있을 때 발생하는 문제
object의 멤버 int a가 있는데, object를 상속한 rectangle과 clickable도 각각 int a를 재정의 할 수 있음. 이때 이 둘을 상속한 button에서 a를 갖고오려면, 어느 클래스의 a를 갖고와야 할 지 모호함. (컴파일 에러)
▷ Mixin Class
다중 상속의 한 형태.
클래스를기능 기준으로 구현해놓고, 필요할 때 갖고와서 사용하는 것임.
Mixin을 사용하면 여러 클래스에 동일한 기능을 쉽게 추가할 수 있슴
- 패런트 클래스"처럼" 작동함
- 서브클래스가 이 functionality를 그대로 갖다씀
보통 상속에서는, “is a” 관계 (: circle is a type of shape. Circle을shape처럼 사용 가능)을 가짐
근데mixin은“is a” 관계없이 기능 사용이 가능하게 만들어 주는 것.
Circle은 Shape의 한 타입이다. (is a)
Circle이 Animator의 한 타입은 아니지만, Animator의 기능인 Animator()을 갖고 있다. (is a 는 아님)
사용 예시
#include <iostream>
#include <string>
// Mixin 클래스 - 로깅 기능만 제공
class LoggerMixin {
public:
void log(const std::string& message) const {
std::cout << "Log: " << message << std::endl;
}
};
// Mixin 클래스 - 식별자 표시 기능 제공
class IDMixin {
public:
IDMixin(int id) : id(id) {}
void showID() const {
std::cout << "ID: " << id << std::endl;
}
private:
int id;
};
// User 클래스 - 필요한 기능을 Mixin으로 조합
class User : public LoggerMixin, public IDMixin {
public:
User(int id, const std::string& name) : IDMixin(id), name(name) {}
void displayInfo() const {
showID(); // IDMixin 기능
std::cout << "Name: " << name << std::endl;
log("Info displayed"); // LoggerMixin 기능
}
private:
std::string name;
};
// Mixin 클래스 사용 예시
int main() {
User user(101, "Alice");
user.displayInfo(); // Mixin 기능 포함하여 정보 출력
return 0;
}
▶ Polymorphism (다형성)
하나의 통일된 인터페이스로 여러 클래스의 작업을 할 수 있도록 하는 것
실행 시간에 Virtual table을 통해서 어느 클래스로 가야하는지 딱 찾아오는 거
즉, 같은 부모의 변수, 함수를 상속받았지만, 조금씩 다른 기능을 할수있게 하는 거
코드가 유연해지고 확장성이 높아져, 여러 객체를 같은 방식으로 처리할 수 있음
예를들어 같은 Attack()이라도, “누가“ Attack하는지에 따라 Attack의 세부 기능이 바뀔 수 있음
따라서 맨 위 부모 클래스에 가상함수 또는 순수가상함수 Attack() 선언해놓고, 자식 클래스들에서 각자 override해서 쓰면 됨
(순수 가상함수: 실제로 구현은 안 되어있지만 상속받을 수 있게 하는 목적으로 만들어 놓은 것)
사용 예시
#include <iostream>
#include <vector>
#include <memory>
class Animal {
public:
virtual void speak() const { // 가상 함수, 각 동물 클래스에서 구현할 함수
std::cout << "Some animal sound" << std::endl;
}
virtual ~Animal() = default; // 기본 소멸자
};
class Dog : public Animal {
public:
void speak() const override { // Dog 클래스에서 구현된 speak
std::cout << "Bark!" << std::endl;
}
};
class Cat : public Animal {
public:
void speak() const override { // Cat 클래스에서 구현된 speak
std::cout << "Meow!" << std::endl;
}
};
int main() {
std::vector<std::unique_ptr<Animal>> animals;
animals.push_back(std::make_unique<Dog>());
animals.push_back(std::make_unique<Cat>());
for (const auto& animal : animals) {
animal->speak(); // 각 동물 클래스의 speak 메서드를 다형성으로 호출
}
return 0;
}
● 근데, if 문으로 구현할 수도 있지 않나? => 가능함. 그러나 어떤 Attack이 실행될 지 모르기 때문에, 실행될 수 있는 모든 Attack에 대해서 알고 있어야 한다는 단점.
ex) 케이스가 한 100개정도 있는데 새로운 애를 확장하려고 할 때! 여기에 이미 있는지 없는지, 어떤 형태로 구현되어 있는지…이런거 다 알아야함 특히 내가 만들지 않은 프로그램 확장하려면 엄청 힘듦 ㅠㅠ
▶ Composition and Aggregation
클래스 간의 관계를 표현할 때 사용하는 두가지 개념.
멤버 클래스의 life time에 관련된 것
Composition은 "owns-a" Aggregation은 "has-a"
//composition
public class Window {
private Rectangle rect = new Rectangle();
}
//aggregation
public class Window {
private Rectangle rect;
void setRect(Rectangle rectParam) {
this.rect = rectParam;
}
}
Window 객체가 해제되는 경우를 생각해보면 차이가 있음.
Composition은 내가 죽으면 rect도 같이 사라짐 (owns-a)
Aggregation은 내가 죽더라도 rect의 실체는 안 사라짐 (has-a)
rectParam으로 받아와서 내 rect로 세팅해 준 것이기 때문 (참조하는 형태)
▶ Design Pattern - Singleton
디자인 패턴이란, 반복되는 문제에 대해 여러 개발자가 유사한 해결 방식을 반복해서 사용하는 경우, 이를 공식화한 소프트웨어 설계 방법을 의미함.
싱글톤이란, 클래스가 하나의 인스턴스만 가지게 하고, 이것을 어디서든 접근 가능하게 만드는 패턴
게임에서 많이 사용되는데, 전체적인 게임 옵션, 스테이지 등의 파라미터 세팅할 때 사용함.