TIL_061 : class/struct, vector메모리, meta, TObjectPtr

김펭귄·2025년 11월 13일

Today What I Learned (TIL)

목록 보기
61/93

오늘 학습 키워드

  • class vs struct

  • vector 메모리 구조

  • 리플렉션 매크로 meta

  • TObjectPtr

1. Class vs Struct

  • 어제 언리얼에서 Class와 Strcut를 리플렉션하기 위한 매크로를 외우다 보니 문득, C++에서 둘의 차이가 궁금해짐

  • 기본적으로 둘의 기본 접근 제한자에 차이가 있다는 것은 알고 있었음. Classprivate이고, Structpublic.
    따라서 Struct는 지금까지 단순한 데이터 저장소(집합)으로 사용했고, Class는 그 이상의 캡슐화를 제공하고 싶을 때 사용했음

추상화

  • 클래스와 구조체 둘 다 추상화 기능(virtual) 제공

  • 아래의 추상화 관련 내용은 구조체와 클래스 둘 다 동일하므로 클래스만 설명

  • 추상화 클래스의 순수가상함수는 자식 클래스에서 무조건 정의햐야한다고 알고 있었는데, 정확히는 인스턴스로 생성하려면 그 자식에서 무조건 해당 순수가상함수를 정의해야함.
    자식에서도 추상화객체로 냅두려면 구현 안 해도 됨

  • 추상클래스 예시

// 추상클래스
struct A {
  virtual void f() = 0;	// 순수가상함수
};

// 구체 클래스
struct B : A {
  virtual void f() { }	// 얘는 정의한 것(그냥 가상함수)
};

// Error: 리턴값 불가능
// A g();

// Error: 매개변수 불가능
// void h(A);

A& i(A&); 

int main() {

// Error:
// 인스턴스 불가능
//   A a;

   A* pa;
   B b;

// Error: 인스턴스 불가능
//   static_cast<A>(b);
}
  • 추상클래스는 인스턴스를 만들 수 없기에 A g();void h(A);같이 함수 리턴값과 매개변수에
    역시 추상객체를 함수 매개변수로 사용할 수 없음

  • 하지만 참조자, 포인터 선언은 가능하므로 A&, A* 둘 다 가능

  • 마찬가지로 static_cast<A>는 인스턴스니까 불가능하고, static_cast<A&>같이 참조자와 포인터는 가능

결론

  • 공부해본 결과, 기존에 알고 있던대로 정말 둘의 차이는 기본 접근지정자가 다른 것말고는 없었음

  • 새로 알게된 것은 상속할 때도 접근지정자가 있는 것으로 상속접근지정자를 설정하지 않으면 기본적으로 structpublic으로 classprivate로 상속한다는 것이다

struct Derived : Base {};	// 기본적으로 public 상속
class Derived : Base {};	// 기본적으로 private 상속
부모 멤버 접근지정자public 상속 시protected 상속 시private 상속 시
publicpublicprotectedprivate
protectedprotectedprotectedprivate
privateX (상속안됨)X (상속안됨)X (상속안됨)
  • 또한 virtual사용 시, 해당 함수는 런타임에 동적으로 바인딩된 함수를 호출하고, override는 안정성을 위해 쓰는것으로 부모함수가 virtual이면 덮어씌우고,아니면 에러를 일으킨다.
    그리고 이는 구조체나 클래스 둘 다 동일하다

  • 모든 함수는 메모리의 Code부분에 기계어로 올라가고, 함수가 호출되면 해당 함수이름을 가지고 포인터로 해당 기계어 부분으로 찾아가 실행.
    부모, 자식 둘 다 같은 이름의 함수를 가져도, 컴파일러가 알아서 다른 함수를 호출해줌

class Parent{ void foo();};	// 이름은 같아도
class Child : Parent { void foo();};	// Code메모리의 다른 위치에 load되어 다르게 실행
  • 부모객체포인터로 자식 객체 가리키면 부모객체에 있는 멤버변수, 함수에만 접근 가능
class Parent { 
public:
	void foo();
};

class Child : public Parent {
public:
	void goo();
};

Parent* p;
p = new Child();
p->foo();	// OK
p->goo();	// error

2. vector 메모리

  • 벡터의 경우 객체 자체는 스택에 생성되지만, 내부 원소는 heap에 생성되어 관리

1차원 vector

vector<int> v = {1, 2};
int* ptr = v.data();	// 첫 번째 원소의 주소를 반환
  • 내부 원소는 heap에 연속적으로 존재하며, v는 이 데이터의 시작 주소, 크기, 버퍼 끝 주소등을 가짐

  • 내부원소는 연속적이기에, 배열처럼 ptr[1], ptr + 1같은 연산 가능

  • 실제 v객체의 크기는 작음. 내부 원소는 heap에 저장되어 있으므로

  • 만약 할당받은 heap보다 원소 개수가 많아지면 새로 할당받고 복사하여 이동

  • 메모리가 이동할 수 있으므로, 반복자나 포인터 사용시 벡터에 추가 삭제 등 변화가 생기면 에러날 수 있음

    vector<int> v(5, 0);
    int* p = v.data();
    v.resize(1000);	// heap 메모리 이동함
    cout << p[1];	// 값 이동되어 쓰레기 값에 접근

2차원 vector

vector<vector<int>> v = {{1, 2, 3}, {4, 5, 6}};
vector<int>* p = v.data();	// 첫 번째 원소가 vector<int>*
  • 마찬가지로, v는 스택에서 vector<int>를 원소로 가지는 벡터의 시작 주소, 크기, 버퍼 끝 주소등을 가짐

  • 그럼 이 벡터들은 또 int를 원소로 가지는 벡터들을 관리하는 객체로 마찬가지로, 각 int벡터의 시작주소, 크기, 버퍼 끝 주소등을 가짐

  • 이 int벡터를 관리하는 행벡터는 행이 더 늘어날 수 있으므로, heap에 생성되며, 이 벡터는 연속적으로 존재

  • 하지만, 이 행벡터의 실제 데이터는 각각 heap에 따로 존재함

  • v[0]~v[n]은 연속적. v[i][0]~v[i][n]도 연속적으로 존재. v[0][n]에서 v[1][n]은 따로 존재

3. 리플렉션 매크로 meta

  • UPROPERTY()UFUNCTION()에 사용되는 meta키워드는 해당 변수나 함수에 메타데이터를 부여해 추가 기능을 제공할 수 있다

자주 사용되는 키워드

  • DisplayName
    에디터에서 변수나 함수가 보이는 이름을 지정. 디자이너에게 보기 좋게 이름 설정이 가능
UPROPERTY(VisibleAnywhere, meta = (DisplayName = "PlayerSpeed"))
float MyCharacterSpeed;
  • ToolTip
    에디터에서 마우스 오버 시 나타나는 툴팁 설명문을 지정. 변수나 함수에 대한 보충 설명
UPROPERTY(EditAnywhere, meta = (ToolTip = "Resets the player stats"))
void ResetStats();
  • ClampMin, ClampMax
    에디터에서 수정할 때 숫자형 변수의 값 범위를 제한함. 정수와 실수만 가능하며, 이 제한은 영원히 걸림
UPROPERTY(EditAnywhere, meta = (ClampMin = "0", ClampMax = "100"))
int Health;
UPROPERTY(EditAnywhere, meta = (ClampMin = "0.0", ClampMax = "10.0"))
float Score;
  • EditCondition
    조건이 참일 때만 해당 변수가 에디터에서 수정 가능
UPROPERTY(EditAnywhere)
bool bCanEdit;

UPROPERTY(EditAnywhere, meta = (EditCondition = "bCanEdit"))
float MoveSpeed;
  • BindWidget
    UMG에서 만든 위젯을 C++변수에 바로 연결해주고 싶을 때 사용.
    다시말해, 순수가상함수를 자식클래스에서 정의해야하듯, 해당 멤버변수에 UMG위젯을 무조건 연결해야함을 의미
    위젯의 이름과 변수의 이름이 동일해야함
UPROPERTY(meta = (BindWidget))
TObjectPtr<UEditableTextBox> EditableTextBox_ChatInput;
위젯 바인딩 안 하면 컴파일 에러 뜸
  • BindWidgetOptional
    위와 다르게 선택적으로 바인딩해도되고, 안 해도 됨. 위젯이 무조건 있어야하는건 아닐 때 사용

  • AllowPrivateAccess
    private 변수를 에디터에서 볼 수 있게 해줌. 다른 매크로키워드와 함께 사용하면 수정도 가능

private:
    UPROPERTY(BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    int32 HP;
  • UIMin, UIMax
    해당 변수가 에디터상에서 입력 가능한 최소,최대값을 지정.
    숫자형 변수(정수, 실수)의 슬라이더, 입력창 등에 적용하여 값의 범위를 제한
    ClampMin, ClampMax과의 차이로는 UIMin, UIMax는 에디터 상에서만 제한을 두고 게임이 실행될 때는 제한이 걸리지 않음.
    ClampMin, ClampMax는 에디터상뿐만이 아니라, 게임이 실행되는 도중에도 제한함
UPROPERTY(EditAnywhere, meta = (UIMin = "0.0", UIMax = "1.0"))
float Opacity;
  • WorldContext
    UFUNCTION 매크로에서 사용되며, 블루프린트에서 호출될 때 자동으로 WorldContextObject를 연결.
    WorldContextObject가 필요하고 블루프린트 노드에서 파라미터 없이 자동으로 연결해주고 싶을 때 사용.
    • 엔진이 내부적으로 적절한 컨텍스트(월드)를 찾아서 연결
    • WorldContext = "변수명"으로 변수명에 연결됨
UFUNCTION(BlueprintPure, meta = (WorldContext = "WorldContextObject"))
static APawn* GetPlayerPawn(const UObject* WorldContextObject, int32 PlayerIndex);

4. TObjectPtr<>

  • 기존 포인터처럼 사용가능하며 더 많은 기능이 추가됨

  • 언리얼 에디터에서 객체 추적, 동적 해상도, 액세스 추적 등을 지원함

  • GC와도 호환되어 인스턴스 사라지면 기존 포인터는 액세스 위반을 일으킨 반면, 이 자료형은 경고나 null처리 등 좀 더 안전하게 이를 처리함

  • 일반 포인터보다 더 안전하고 여러 기능이 추가된 포인터이므로 이걸 사용할 것

  • UPROPERTY() 꼭 해줘야 엔진이 추적하여 GC가 삭제 안 함

// 헤더
UPROPERTY()
TObjectPtr<int32> a;

// cpp
*a = 10;	// 일반 포인터처럼 사용
profile
반갑습니다

0개의 댓글