char
문자 하나를 저장하는 데이터 타입이지만 내부적으로는 아스키코드에 해당하는 정수값으로 저장된다.
따라서 char를 int로 변환하면 아스키코드 값을 반환한다.
char to int의 간단한 방법
char c = '5';
int i = c - '0';
메모리는 한정된 자원이다.
자동으로 회수된다.단점
new 연산자를 사용하고 해제 시 delete연산자를 사용한다.delete로 해제할 때까지 유지된다.new는 주소를 반환하기 때문에 lvalue가 포인터여야한다.new <type>[number]: 힙 메모리를 배열처럼 사용할 수 있다. 이렇게 할당한 메모리는 delete[]으로 해제해야 한다.
int *p1 = new int;
delete p1;
int *p2 = new int[100];
delete[] p2;
// delete p2; // error
❗이미 해제된 메모리의 주소를 계속 가지고 있는 포인터
포인터는 가리키는 메모리가 해지되었는지 알 수 없음
해당 포인터의 주소를 읽기만 할때는 정상 종료는 될 수 있으나 제어하면 에러가 발생한다.
이중 해지도 위험!
동적 할당한 메모리를 사용 후 해제하지 않으면 사용하지 않는 메모리가 쌓이고 결국 메모리가 부족해질 수 있다.
Dangling Pointer가 발생하지 않도록 관리해주는 역할
new, delete를 사용하지 않는 자동 메모리 관리이다.
스마트 포인터는 객체에 대해 소유권을 갖는다.
메모리 해제 책임을 명확하게 하고, 자동으로 처리하기 위해서
스마트 포인터는 포인터처럼 작동하는 클래스 객체이다.
단일 소유권을 관리한다.
여러 포인터가 동시에 하나의 주소를 소유할 수 없다. move로 소유권을 이전할 수 있다.
여러 포인터가 하나의 주소를 소유할 수 있다.
레퍼런스 카운터로 몇개의 포인터가 소유하고 있는지 관리한다.
레퍼런스 카운트가 0이되면 메모리가 해지된다.
use_count(): 현재 객체를 참조하는 포인터의 수 출력
reset(): 소유 중인 객체를 해제하거나 다른 객체로 변경
순환 참조
두 개 이상의 객체가 서로 shared_ptr를 가리켜 참조하는 상황으로 메모리 누수를 유발한다.
다른 스마트 포인터와 다르게 레퍼런스 카운트를 증가시키지 않는 약한 참조를 한다.\
순환 참조일 때 서로 순환하고 있는 shared_ptr 중 하나를 weak_ptr로 대체하면 순환 고리가 끊어져 문제를 해결할 수 있다.
lock(): 내부 객체 유효성 확인 메소드. 약한 참조는 유효성 확인하고 사용해야한다.
∵ weak_ptr는 자기 자신이 소유할 수 없고 shared_ptr를 통해서 간접적으로 관찰하기 때문에
'lock()'은 유효할 경우 shared_ptr<객체>를 반환한다. 아니면 nullptr에 해당하는 shared_ptr를 반환한다.
#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() {
//a_shared에 a_ptr.lock()의 반환값을 넣고 a_shared가 nullptr인지 확인하는 부분
if (auto a_shared = a_ptr.lock()) { // 유효한지 확인
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(); // A가 유효하므로 Hello 출력
} // A는 scope을 벗어나며 소멸됨
b->useA(); // A는 이미 소멸되었기 때문에 메시지 출력
}
얕은 복사: 클래스 내의 포인터 멤버를 복사할 때 포인터의 주소만 복사하는 것. dangling pointer가 발생할 수 있다.
깊은 복사: 클래스의 포인터 멤버가 가리키는 동적 데이터를 새로 할당된 독립된 메모리에 복제하는 것. 포인터나 동적으로 할당된 자원을 관리하는 객체는 메모리 안정성을 위해 깊은 복사를 사용하는 것이 좋다.
#include <iostream>
using namespace std;
int main() {
// 포인터 A가 동적 메모리를 할당하고 값을 30으로 설정
int* A = new int(30);
// 포인터 B가 A가 가리키는 값을 복사 (깊은 복사)
int* B = new int(*A);
cout << "A의 값: " << *A << endl; // 출력: 30
cout << "B의 값: " << *B << endl; // 출력: 30
// A가 동적 메모리를 해제
delete A;
// B는 여전히 독립적으로 자신의 메모리를 관리
cout << "B의 값 (깊은 복사 후): " << *B << endl; // 출력: 30
// B의 메모리도 해제
delete B;
return 0;
}
가비지 컬렉션 시스템으로 객체들의 메모리 관리를 자동화한다.
더이상 사용하지 않는 객체들의 메모리를 알아서 해제한다.
UObject를 식별하여(Maek) 메모리에서 제거(Sweep)한다. 루트셋(가비지 컬렉션 대상이 아님)에서 시작해서 사용 중인 UObject를 마크하고 완료되면 마크되지 않은 객체들의 메모리를 회수한다. 이 과정에서 해당 객체의 소멸자가 호출되고 메모리가 반환된다.GUObjcectArray라는 전역 배열을 통해 게임의 오브젝트를 관리하는데 거기에서 플래그를 통해 객체 정보의 일부로 관리된다.AddToRoot(),RemoveFromRoot()BeginDestroy()함수가 호출된 상태를 나타낸다. 객체가 메모리에서 해제되기 전에 필요한 정리 작업을 수해하는 함수. FinishDetroy()함수가 호출되었음을 나타낸다. 객체 소멸의 마지막 단계로 이후 객체의 메모리가 완전히 해제된다.리플렉션 시스템
| 리플렉션 매크로 | 목적 | 일반적인 위치 |
|---|---|---|
| UCLASS() | C++ 클래스를 UObject 기반의 리플렉션 시스템에 등록 | 클래스 정의 앞 |
| UPROPERTY() | 멤버 변수를 리플렉션 시스템에 노출 | 멤버 변수 선언 앞 |
| UFUNCTION() | 멤버 함수를 리플렉션 시스템에 노출 | 멤버 함수 선언 앞 |
| USTRUCT() | C++ 구조체를 리플렉션 시스템에 등록 | 구조체 정의 앞 |
| GENERATED_BODY() | UHT가 생성하는 리플렉션 및 엔진 지원 코드를 위한 삽입 지점 | 클래스/구조체 본문 첫 줄 |