본문 바로가기
프로그래밍/Unreal 부트캠프

TIL 2024.12.30 기록

by Rozentea 2024. 12. 30.

0. 개요


오늘은 이전에 잘 사용하지 않았던 스마트 포인터, 그리고 알고있던 개념이었지만 이름을 몰랐던 댕글링 포인터, static 키워드에 대한 정리 등을 했다.

특히 과제를 진행하면서 constexpr이라는 키워드를 처음 공부하게 되었는데, 예전이었으면 이해를 못했을 내용인데, 지금은 이해가 가는것 같아서 뿌듯했다.

 

오늘도 간단하게 어떤것들을 공부하고 알았는지에 대해 작성하고, 과제를 올린 깃허브 주소를 올릴 것이다.

 

1. 댕글링 포인터 (Dangling Pointer)


댕글링 포인터 : 이미 해제된 메모리 주소를 여전히 가리키고 있는 포인터

즉, 포인터가 가리키는 메모리가 더는 유효하지 않을 때를 이야기한다.

댕글링 포인터는 premature free(조숙한 해제, 너무 급한 해제)라고 부르기도 한다.

 

댕글링 포인터가 야기할 수 있는 문제

1. 메모리 접근시 예측 불가능한 동작

2. 메모리 접근 불가 시 Segmentation fault 발생

3. 잠재적인 보안 위험

4. 메모리 이중 반환

 

이러한 댕글링 포인터는 다음과 같은 경우에 발생할 수 있다.

1. 메모리 해제 후, 해제된 메모리에 접근

2. 함수 호출에서 자동 변수를 가리키는 포인터의 반환

 

댕글링 포인터를 예방하기 위한 방법

1. 메모리 해제 후 포인터를 nullptr로 설정한다.

2. free함수를 대체할 새로운 함수를 작성해 사용한다.

2. 스마트 포인터


위와 같은 댕글링 포인터를 미리 방지하기 위해 스마트 포인터를 사용하기도 한다.

스마트 포인터의 핵심원리는 레퍼런스 카운터이다.

delete를 사용자가 직접하는게 아니라 스마트 포인터 측에서 참조하고 있는 포인터의 개수가 0이되면 자동으로 해지해주는 방식이다.

 

스마트 포인터를 사용하기 위해서는 <memory>라이브러리를 include 해주어야 한다.

 

unique_ptr

레퍼런스 카운터가 최대 1인 스마트 포인터입니다.

따라서 소유권을 다른 사람에게 주는 건 가능하나, 동시에 두개 이상 소유할 수 없습니다.

 

1. 최대 레퍼런스 카운터가 1이기 때문에 복사 혹은 대입이 되지 않습니다.

   이걸 시도하는 순간 컴파일 에러가 발생한다.

2. 대입은 레퍼런스 카운터가 2가 되므로 불가능 하지만 소유권 이전은 가능합니다.

   move를 사용해서 소유권 이전을 할 수 있습니다.

#include <iostream>
#include <memory>
using namespace std;

class MyClass {
public:
    MyClass(int val) : value(val) {
        cout << "MyClass 생성: " << value << endl;
    }
    ~MyClass() {
        cout << "MyClass 소멸: " << value << endl;
    }
    void display() const {
        cout << "값: " << value << endl;
    }
private:
    int value;
};

int main() {
    // unique_ptr로 MyClass 객체 관리
    unique_ptr<MyClass> myObject = make_unique<MyClass>(42);

    // MyClass 멤버 함수 호출
    myObject->display();

    // 소유권 이동
    unique_ptr<MyClass> newOwner = move(myObject);

    if (!myObject) {
        cout << "myObject는 이제 비어 있습니다." << endl;
    }
    newOwner->display();

    // 범위를 벗어나면 newOwner가 관리하는 메모리 자동 해제
    return 0;
}

 

shared_ptr

레퍼런스 카운터가 N개가 될 수 있는 스마트 포인터이다.

따라서 레퍼런스 카운터 갯수를 볼 수 있는 use_count()와 현재 포인터를 초기화 할 수 있는 reset()을 제공한다.

 

1. 최대 레퍼런스 카운터가 N이기 때문에 복사 혹은 대입이 가능하다.

#include <iostream>
#include <memory> // shared_ptr 사용
using namespace std;

int main() {
    // shared_ptr 생성
    shared_ptr<int> ptr1 = make_shared<int>(10);

    // ptr1의 참조 카운트 출력
    cout << "ptr1의 참조 카운트: " << ptr1.use_count() << endl; // 출력: 1

    // ptr2가 ptr1과 리소스를 공유
    shared_ptr<int> ptr2 = ptr1;
    cout << "ptr2 생성 후 참조 카운트: " << ptr1.use_count() << endl; // 출력: 2

    // ptr2가 범위를 벗어나면 참조 카운트 감소
    ptr2.reset();
    cout << "ptr2 해제 후 참조 카운트: " << ptr1.use_count() << endl; // 출력: 1

    // 범위를 벗어나면 ptr1도 자동 해제
    return 0;
}

3. static 키워드


 

 

[C++] static 키워드 완벽 가이드

들어가며 C++에서 static 키워드는 간단한 사용법을 가지지만 복잡한 의미를 가지고 있는 아이러니한 키워드다. 함수든 변수든 간단하게 static이라는 키워드 여섯글자만 붙여 주면 무엇이든 static

kukuta.tistory.com

 

정리가 너무 잘 되어있다.

 

4. constexpr


 

 

constexpr(C++)

C++ 언어 constexpr 키워드에 대한 안내.

learn.microsoft.com

constexpr은 컴파일 타임 상수이다.

주요 특징

● 컴파일 타임에 값이 계산된다.

   즉, 프로그램 실행전에 이미 계산되어 결과가 결정되기 때문에 실행 시 성능 향상을 가져올 수 있다.

● constexpr 변수는 초기화될 때 상수 값이어야 한다.

   즉, 이후에 값을 변경할 수 없다.

● constexpr 함수는 컴파일 타임에 호출되며, 호출 시점에 결과를 계산할 수 있다.

   이는 일반 함수와 다르게 실행 시점이 아니라 컴파일 시점에 값을 계산할 수 있다는 장점이 있다.

 

즉, constexpr은 성능 최적화나 컴파일 타임에 상수 값을 미리 계산할 필요가 있을 때 사용된다.

이를 통해 프로그램 실행시간을 줄이고, 코드의 효율성을 높일 수 있다.

 

if constexpr은 조건에 따른 분기를 컴파일 타임에 결정하도록 한다.

● 컴파일 타임에 조건식을 평가하여 해당 조건이 true일 때만 해당 블록을 컴파일하고, false일 때는 그 블록을 완전 무시한다.

● 즉, 컴파일 타임에 조건을 확인해, 필요한 코드만 컴파일 하기 때문에 컴파일된 바이너리를 최적화할 수 있다.

 

 

 

템플릿은 컴파일할 때 실질적인 T에 대응하는 클래스가 생성되기 때문에 if constexpr로 T가 포인터인지 아닌지를 컴파일 타임에 검사해 동작할 수 있다.

5. is_pointer


형식이 포인터인지 아닌지 구분할 수 있는 클래스.

template <class Ty>
struct is_pointer;

템플릿으로 만들어져 있으며, <Ty>에 우리가 확인할 대상을 넣어주고, 멤버인 value를 확인해 true, false 체크를 하면 된다.

 

 

is_pointer 클래스

자세한 정보: is_pointer 클래스

learn.microsoft.com

 

~SimpleVector()
	{
		// T 타입이 포인터일 경우
		if constexpr (is_pointer<T>::value)
		{
			// 배열 내에 있는 동적할당된 객체 주소를 모두 반환해준다.
			for (int i = 0; i < currentSize; ++i)
			{
				delete data[i];
			}
		}
		
		// data 배열 삭제
		delete data;
	}

 

 

GitHub - Blanquette0315/Sparta_Homework_03: Sparta_Homework_03

Sparta_Homework_03. Contribute to Blanquette0315/Sparta_Homework_03 development by creating an account on GitHub.

github.com

 

템플릿을 이용해 vector를 구현하는 과제를 하다가, T가 포인터 타입일 경우와 그렇지 않은 경우 처리해야하는 방식이 달라서 어떻게 구현할지 머리 아팠었다.

그때, 찾아보니 constexpr과 is_pointer를 알게되었다.

 

템플릿을 만들 때, 위 두가지는 정말 잘 쓰일 것 같다.

6. const &


SimpleVector(const SimpleVector& OtherVector)
		: data(nullptr)
		, currentSize(OtherVector.currentSize)
		, currentCapacity(OtherVector.currentCapacity)
	{
		data = new T[currentCapacity];

		for (int i = 0; i < OtherVector.size(); ++i)
		{
			data[i] = OtherVector[i];
		}
	}

복사 생성자를 구현할 때, const SimpleVector& OtherVector는  인자로 레퍼런스를 받아 복사 비용이 발생하지 않고, const 키워드가 붙었기 때문에 수정이 불가능하다는 것은 이해하고, 알고 있었던 내용이었다.

 

하지만, OtherVector.size()에서 문제가 발생했는데, const 객체에서는 const 멤버 함수만 호출 할 수 있기 때문이었다...!

+ 연산자 오버로딩도 마찬가지이기 때문에 []에서도 동일한 문제가 발생했다.

 

const 멤버 함수 :

● 객체의 상태를 변경하지 않는 함수이다.

● const 객체에서 호출할 수 있다.

● 함수 선언 뒤에 const를 붙여서 정의한다.

int size() const
{
	// ...
}

 

비 const 멤버 함수:

● 객체의 상태를 변경할 수 있다.

● const 객체에서는 호출할 수 없다.

 

7. 마치며


템플릿을 이용해 SimpleVector 구현하기 과제를 진행하면서 if constexpr을 어떻게 사용해야할지 컴파일 타임이 어느 시점인지 등을 좀 더 자새히 알게되어 좋았다.

 

예전에 진행했던 프로젝트 3개다 템플릿을 잘 사용하지 않았었다.

 

때문에 템플릿을 공부했지만 잘 몰랐고, 어떻게 사용해야할지 감이 잘 안왔는데,

오늘 과제를 진행하면서 템플릿을 어떻게 활용해야하고, 템플릿을 여러 상황에 대응할 수 있게 만들려면 어떤 고민을 해야하는지,

그리고 그런 고민을 해결하기 위해 존재하는 키워드나 함수들은 무엇이 있는지 알게되어 의미있는 하루였다.

 

보니까 제공해주는 vector도 constexpr 키워드로 작성된 것들이 꽤 있던것을 보면, 템플릿을 작성할 때 유용한 키워드인 것 같다.

또, 굳이 템플릿이 아니더라도, 최적화를 위해서 사용할 가치도 충분해보인다.

'프로그래밍 > Unreal 부트캠프' 카테고리의 다른 글

TIL 2025.01.02 기록  (0) 2025.01.02
TIL 2024.12.31 기록  (1) 2024.12.31
TIL 2024.12.27 기록  (0) 2024.12.27
TIL 2024.12.26 기록  (0) 2024.12.26
TIL 2024.12.24 기록  (1) 2024.12.24