2026-03-05 TIL (8일차)
C++ 강의 (2주차)
스마트 포인터
원리: 스마트 원리의 핵심 원리는 new / delete를 사용하지 않는 자동 메모리 관리unique_ptr
● 힙 메모리에 대해 독점적 소유권을 가지는 스마트 포인터 (주소 하나에 포인터 하나)
● 소유권 이전은 가능하지만 동시에 가리키는거는 불가shared_ptr
● 하나의 자원(힙 메모리)을 여러 포인터가 공동 소유하며, 참조 횟수(Reference Count)가 0이 될 때 자동으로 메모리를 해제하는 포인터
● 참초 횟수: 소유하고 있는 포인터 갯수
● 문제점: 순환참조를 하게되면 해제가 불가능 하다. (힙 메모리에 내용을 파괴할 때 자신을 가리키고 있는 포인터가 아직 남아있으므로 계속 남아있게 된다.)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
class Cat { public: Cat(std::string name) : mName{ name } { std::cout << mName << " cat constructor" << std::endl; } ~Cat() { std::cout << mName << " cat destructor" << std::endl; } std::shared_ptr<Cat> mVar; private: std::string mName; }; int main() { std::shared_ptr<Cat> kitty = std::make_shared<Cat>("kitty"); std::shared_ptr<Cat> nabi = std::make_shared<Cat>("nabi"); kitty->mVar = nabi; nabi->mVar = kitty; std::cout << "kitty count : " << kitty.use_count() << std::endl; std::cout << "nabi count : " << nabi.use_count() << std::endl; }
weak_ptr (shared_ptr과 연관이 있음)
● 객체를 가리키기는 하지만, 소유권(참조 횟수)을 주장하지 않는 지켜보기 전용 포인터
● shared_ptr을 가리키고 있는 주소값을 똑같이 weak_ptr이 가리키게 해도 참조 횟수가 증가하지 않는다.
● shared_ptr의 문제점인 순환참조가 안일어나게 할 수 있다.
● weak_ptr에서 lock()은 객체가 살아있는지 확인하고, 살아있다면 내가 안전하게 사용할 수 있도록 ‘임시 소유권’을 빌려오는 기능이고 사용이 끝나면 빌려온 권한은 자동으로 사라지고, 객체는 다시 언제든 파괴될 수 있는 상태로 돌아간다.
Unreal
언리얼 엔진 메모리관리
가비지 컬렉션
더 이상 아무도 쓰지 않는 메모리(객체)를 엔진이 알아서 청소해 주는 시스템
루트셋(Root Set)에 제외한 모든 객체는 가비지 컬렉션에 들어감
루트셋: 무조건 삭제시키지말고 놔둬야한다는 최상위 객체 목록
ex) Game Instance, World, Player Controller, Engine Subsystems 등
언리얼 엔진 메모리 관리 객체 분류
| 구분 | 가비지컬렉션 수거 여부 | 판단 기준 | 예시 |
|---|---|---|---|
| 생존 객체 | 제외 | 루트셋 그 자체이거나, 루트셋으로부터 참조(UPROPERTY)이 연결되어 있음 | 루트셋, 참조된 객체 |
| 가비지 객체 | 대상 | 루트셋으로부터 연결된 참조가 없는 상태 | Destroy된 액터, 참조를 잃은 UObject, 함수 안의 지역 변수, 생성만 하고 어디에도 등록 안 한 UI |
마크 앤 스윕 알고리즘
첫번째 단계: 루트셋에서 포함된 객체 식별
두번째 단계: 루트셋 객체에서 직간접적으로 참조하는 객체를 판단하고 마크함
세번째 단계: 마크 단계가 완료되면 마크되지 않은 객체들이 차지하고 있던 메모리를 회수
가비지 컬렉션은 단순히 지운다,안 지운다를 넘어, 각 객체의 상태를 플래그(Flag)라는 꼬리표로 관리
● RF_RootSet: 개발자가 직접 수동으로 관리하겠다.수동으로 지우기 전까지 건드리지 마라는 플래그
● RF_BeginDestroyed(시스템이 내부적으로 관리하는 값)
파괴 로직을 막 시작한 단계, BeginDestroyed() 가상 함수가 호출되었음을 의미
이 상태의 객체는 아직 메모리에 데이터는 남아있지만, 더 이상 정상적인 기능을 수행할 수 없습니다.
● RF_FinishDestroyed(시스템이 내부적으로 관리하는 상태 값)
모든 파괴 준비 작업이 끝난 단계, inishDestroyed() 가상 함수가 호출되었음을 의미
객체가 차지하던 공간을 비우기 직전의 마지막 단계, 이 플래그가 찍히면 더 이상 엔진의 어떤 시스템도 이 객체에 접근해서는 안 됨.
리플렉션 시스템
엔진이 코드를 실시간으로 인식하고 에디터 노출·가비지 컬렉션·블루프린트와 연동할 수 있게 만드는 통역 시스템
(기본 c++로 하면 언리얼 엔진에서 못받아들이고 사용을 못함)
C++ 공부
상속받은 클래스의 생성자 정의
● 자식 클래스의 생성자는 부모 클래스의 멤버까지 초기화(생성자 호출)해야 할 의무가 있다.
● 자식은 부모의 구성까지 생성이 되므로 자식 클래스 생성자는, 부모 클래스의 생성자를 호출해서 부모 클래스의 멤버를 초기화 하는 것이 좋다.
자식 클래스 생성 과정
● 자식 클래스의 객체생성 과정에서 부모 클래스의 생성자는 100% (대부분 먼저)호출된다.
● 자식 클래스의 생성자에서 부모 클래스의 생성자 호출을 명시하지 않으면, 기초 클래스의 void 생성자가 호출된다.
자식 클래스 객체의 소멸과정
● 자식 클래스의 객체가 소멸될 때에는, 자식 클래스의 소멸자가 실행되고 난 다음에 부모 클래스의 소멸자가 실행된다.
● 스택에 생성된 객체의 소멸순서는 생성순서와 반대이다.
상속 형태의 3가지
| 접근 제한자 | 본인 클래스 | 자식 클래스 | 외부 |
|---|---|---|---|
| public | ✅ 가능 | ✅ 가능 | ✅ 가능 |
| protected | ✅ 가능 | ✅ 가능 | ❌ 불가 |
| private | ✅ 가능 | ❌ 불가 | ❌ 불가 |
Is-A와 Has-A차이
| 구분 | IS-A (상속) | HAS-A (구성) |
|---|---|---|
| 관계 | 클래스 간의 수직적 관계 | 객체 간의 수평적/포함 관계 |
| 결합도 | 강함 (부모가 바뀌면 자식도 바뀜) | 약함 (부품만 교체하면 됨) |
| 유연성 | 낮음 (설계 시점에 결정됨) | 높음 (실행 중에 바꿀 수 있음) |
| 코드 재사용 | 부모의 코드를 그대로 물려받음 | 필요한 기능을 가진 객체를 가져다 씀 |
1
2
3
4
5
6
class A
{
}
class B : public A
{
}
1
2
3
4
5
6
7
class A
{
}
class B
{
A* aptr
}