
스택
힙
둘의 차이
발생 원인
해결법
#include <memory>
unique_ptr<int> ptr1 = make_unique<int>(10);
unique_ptr<int> ptr2 = move(ptr1);
#include <memory>
shared_ptr<MyClass> obj1 = make_shared<MyClass>(42);
shared_ptr<MyClass> obj2 = obj1;
cout << "obj1과 obj2의 참조 카운트: " << obj1.use_count() << endl; // 출력: 2
obj2->display(); // 출력: 값: 42
// obj2를 해제해도 obj1이 객체를 유지
obj2.reset();
cout << "obj2 해제 후 obj1의 참조 카운트: " << obj1.use_count() << endl; // 출력: 1
#include <iostream>
#include <memory>
class A{
public:
void say_hello(){
std::cout << "Hello from A\n";
}
};
class B{
public:
std::weak_ptr<A> a_ptr;
void useA(){
if (auto a_shared = a_ptr.lock()){ // a_ptr이 유효한지 확인한다.
a_shared->say_hello();
} else {
std::cout << "A is no longer available.\n";
}
}
};
int main(){
std::shared_ptr<B> b = std::make_shared<B>();
{
std::shared_ptr<A> a = std::make_shared<A>();
b->a_ptr = a;
b->useA();
}
b->useA();
}
원인
해결법
얕은 복사
class Shallow{
public:
int* data;
Shallow(int value){
data = new int(value);
}
~Shallow(){
delete data;
}
}
Shallow obj1(10);
Shallow obj2 = obj1; // data 포인터만 복사
// obj1.data와 obj2.data가 같은 주소!
// obj2 소멸 시 delete 실행
// obj1 소멸 시 이미 해제된 메모리 delete -> 크래시!
깊은 복사
class Deep{
public:
int* data;
Deep(int value){
data = new int(value);
}
// 복사 생성자: 깊은 복사
Deep(const Deep& other){
data = new int(*other.data); // 새 메모리 할당 후 값 복사
}
// 복사 대입 연산자 : 깊은 복사
Deep& operator=(const Deep& other){
if(this!=&other){
delete data; // 기존 메모리 해제
data = new int(*other.data); // 새 메모리 할당 후 값 복사
}
return *this;
}
~Deep(){
delete data;
}
}
Deep obj1(10);
Deep obj2 = obj1; // 독립적인 메모리 할당
// obj1.data와 obj2.data는 다른 주소
// 각각 안전하게 소멸 가능
UObject와 가비지 컬렉션
UCLASS()
class AMyActor:public AActor{ // AActor는 UObject상속
GENERATED_BODY()
public:
UPROPERTY()
UMyComponent* MyComponent; // UPROPERTY로 마킹
}
핵심 원칙
이 포인터를 추적해줘라고 알림중요 규칙
UObject* 포인터는 반드시 UPROPERTY() 매크로 사용런타임에 클래스, 함수, 프로퍼티 정보를 동적으로 조회하고 조작할 수 있는 시스템
UCLASS()
주요 지정자
UPROPERTY()
UFUNCTION()
USTRUCT()
리플렉션의 장점
주의 사항
내 질문
램 크기가 현재 평균적으로 32gb도 많이 보급하고 있고 64에서 많게는 100기가가 넘게다는 경우도있는데 이럴때도 스택메모리는 1~8MB정도 밖에안하는건지
답변
내 질문
스택 메모리는 힙보다 빠르고 크기가 작고 수명이 짧다는데, 1. 왜 빠른거고 얼마나 빠른건지 2. 수명은 차이가 왜 나는지? (변수가 블록에서 나가면 수명이 다해서 그런건지)
답변
스택 :
내 질문
int* getDanglingPointer(){
int localVal = 42;
return &localVal;
}
int main(){
int* ptr = getDanglingPointer();
cout << *ptr << endl; // 정의되지 않은 동작
// 다른 함수 호출
someOtherFunction();
cout << *ptr << endl; // 이제 완전히 다른 값 나옴
}
자주있다.
질문
언리얼엔진 5공식 유튜브에 33원정대 개발자가 인터뷰한것을 봤는데 비개발자인 사람과 협업하기 위해서 모든 로직을 블루프린트로 짰다고 인터뷰했는데 댓글에서 보니까 오해하기 쉬운 인터뷰라고 하는 사람이 있었다. 마치 모든 로직을 블루프린트로 만들수는 없다는것처럼 이런 것들을 보아하니 C++로 만드는 부분과 블루프린트로 만드는 부분이 나눠져있는거같은데 C++, 블루프린트를 어느 경우에 사용하는지??
답변
모든 로직을 블루프린트로 작성했다는것은 게임 로직(게임 플레이코드)를 의미할 가능성이 크다.
블루프린트는 인터프리터 방식이라 C++보다 느리다.
// 매 프레임 수백~수천 번 실행되는 로직은 C++로
void Tick(float DeltaTime){
// 1000개의 적 AI 업데이트
for(AEnemy* Enemy : AllEnemies){
Enemy->UpdatePathfinding();
}
}
성능차이가 약 5배~10배 날 수있다.
// 복잡한 물리계산, AI경로 탐색 등
TArray<FVector> CalculateOptimalPath(FVector Start, FVector End){
// A* 알고리즘, 수백 개의 노드 검사
// 블루프린트로 하면 노드만 수백개
}
UFUNCTION(Server, Reliable)
void ServerRPC_UpdatePosition(FVector NewPos){
// 대역폭 최적화, 압축 등
}
// 인벤토리 시스템, 스탯 시스템 등 기반 클래스
UCLASS(Blueprintable) // 블루프린트에서 상속 가능하게
class UInventoryComponent: public UActorComponent{
GENERATED_BODY()
UFUNCION(BlueprintCallable)
void AddItem(UItem* Item); // C++로 기능 제공
}
레벨 디자이너가 문 여는 로직 테스트:
- 블루프린트: 5분 만에 만들고 바로 테스트
- C++: 코드 작성 → 컴파일 (5~10분) → 테스트
플레이어가 트리거 진입 → 문 열림 → 사운드 재생 → 이펙트 생성
이런 순차적/이벤트 기반 로직은 블루프린트가 훨씬 직관적입니다.
// C++로 기능 제공
UFUNCTION(BlueprintCallable)
void PlaySkillEffect(int SkillID);
// 디자이너가 블루프린트에서
// 어떤 스킬에 어떤 이펙트?
// 타이밍은?
// 사운드는?
// 모두 블루프린트로 조합
HP : 100 -> 실시간으로 150으로 변경 -> 바로 테스트
C++로 하면 컴파일이 필요
| 기준 | C++ | Blueprint |
|---|---|---|
| 성능 중요 (Tick, 반복문) | ✅ | ❌ |
| 복잡한 알고리즘 | ✅ | ❌ |
| 시스템/프레임워크 | ✅ | ❌ |
| 네트워크 세밀한 제어 | ✅ | ❌ |
| 빠른 프로토타이핑 | ❌ | ✅ |
| 이벤트 기반 로직 | ❌ | ✅ |
| 비개발자 협업 | ❌ | ✅ |
| 자주 조정되는 값 | ❌ | ✅ |
결론
BlueprintCallable, Blueprintable등으로 블루프린트에 노출이런느낌??~~
답변
새로운 객체를 생성하면서 초기화할 때 호출
class MyClass{
private:
int* data;
int size;
public:
// 일반 생성자
MyClass(int s): size(s){
data = new int[size];
cout << "생성자 호출\n";
}
// 복사 생성자(Copy Constructor)
// const 참조로 받고, 같은 클래스 타입
MyClass(const MyClass& other){
size = other.size;
data = new int[size]; // 새 메모리 할당
// 값 복사
for (int i = 0; i < size; i++){
data[i] = other.data[i];
}
}
~MyClass(){
delete[] data;
cout << "소멸자 호출\n";
}
};
MyClass obj1(5); // 일반 생성자
MyClass obj2 = obj1; // 복사 생성자 호출(새 객체 생성)
MyClass obj3(obj1) // 복사 생성자 호출(새객체 생성)
void function(MyClass obj){ // 복사 생성자 호출(값으로 전달)
//
}
function(obj1);
MyClass createObject(){
MyClass temp(10);
return temp; // 복사 생성자 호출
}
const 로 원본 객체를 변경하지 않겠다고 선언하고
& : 참조로 받음( 복사 생성자를 또 호출하는 무한루프 방지)
반환 타입 없음(생성자)
MyClass obj2 = obj1은 왜 복사 생성자인가?
선언과 동시에 초기화이기 때문이미 존재하는 객체에 다른객체의 값을 할당할 때 호출
class MyClass{
private:
int* data;
int size;
public:
MyClass(int s) : size(s){
data = new int[size];
}
// 복사 대입 연산자
// 참조를 반환하고, const참조를 받음
MyClass& operator = (const MyClass& other){
cout << "복사 대입 연산자 호출\n";
// 자기 자신인지 확인
if (this == &other){
return *this; // 자기 자신이면 본인 반환(아무것도 안함)
}
// 기존 메모리 해제
delete[] data;
// 새 메모리 할당(깊은 복사)
size = other.size;
data = new int[size];
// 값 복사
for (int i = 0; i <size; i++){
data[i] = other.data[i];
}
// *this반환 (연쇄 대입을 위해)
return *this
}
~MyClass(){
delete[] data;
}
}
// 복사 대입 연산자가 호출되는 경우
MyClass obj1(5); // 일반 생성자
MyClass obj2(10); // 일반 생성자
obj2 = obj1 // 복사 대입 연산자 호출(이미 존재하는 obj2에 할당)
// 연쇄 대입 가능
MyClass obj3(15);
obj3 =obj2 = obj1; // operator= 반환값이 참조라서 가능
답변
class Base{
public:
virtual ~Base() {} // 필수(virtual로 선언)
};
class Derived: public Base{
private:
int* data;
public:
Derived(){
data = new int[100];
}
~Derived(){
delete[] data;
}
};
// 이렇게 사용할 가능성이 있으므로
Base* ptr = new Derived();
delete ptr; // virtual소멸자가 없으면 Derived 소멸자 호출 안 됨!
추천 접근법
int a = 5;
int b = 10;
int c = a+b; // 연산자 사용 가능
MyClass obj1;
MyClass obj2;
MyClass obj3 = obj2 + obj1; // 연산자 사용 가능
MyClass안에서 MyClass& operator = (const MyClass& other){ ... } 같은 복사 대입 연산자를 사용했기 때문에 MyClass에 한해서는 = 대입연산자를 오버로드해서 obj2 = obj1같은 식이 가능한것이다