TIL - 09 (싱글턴, lvalue, rvalue, 우측값 ,이동, 가상함수테이블, Final)

jh Seo·2024년 8월 6일

싱글턴 패턴

클래스에서 instance가 하나만 유지되는 패턴

static FManagerClass* Get()
{
	static std::unique_ptr<FManagerClass> Instance{ new FManagerClass };
	return Instance.get();
}

위처럼 static으로 인스턴스를 생성 후,
static 함수 Get()을 이용해 인스턴스를 호출한다.

대부분 프로그램이 종료될때까지 유지되도록 구현하지만
원하는 시점에 소멸하도록 구현할 수 있다.

static FManagerClass* Get(bool bDestroy = false)
{
	static std::unique_ptr<FManagerClass> Instance{ new FManagerClass };
	if (bDestroy)
	{
		Instance.reset();
		return nullptr;
	}
	return Instance.get();
}
FManagerClass::Get(true);

이런식으로 true값 들어가면 reset함수를 통해 소멸시켜준 후, nullptr을 반환하도록 할 수 있다.

l-value, r-value

l- value는 이름이 있는 변수이다.
r-value는 l-value가 아닌 나머지로, 리터럴(상수), 임시객체등을 말한다.
ex)
int a = 3+1;
여기서 a는 l-value변수이고 3+1은 r-value이다.
const char* Hello = "Hello";
여기서 Hello는 l-value변수이고, "Hello"는 r-value이다.

우측값(r-value)레퍼런스&&, std::move(), 이동생성자

	// 이동 생성자(구현하지 않으면 복사 생성자가 호출)
	FClass2(FClass2&& InOther) noexcept
		: a(InOther.a)
		, Pointer(InOther.Pointer)
	{
		InOther.a = 0;
		InOther.Pointer = nullptr;
	}

&&는 우측값을 받을 수 있는 레퍼런스 연산자이다 .
&을 두 번 사용하고, 임시객체가 소멸하지 않도록 한다.
기존 복사생성자의 경우 임시객체를 생성 후, 복사하는 과정이 필요하다.
하지만 우측값 레퍼런스를 사용 시, 생성한 임시객체를 바로 사용할 수 있게 함으로
매우 간단해진다.

std::move()함수는 l-value를 r-value로 타입캐스팅해주는 함수이다.

FClass2 ClassMove = static_cast<FClass2&&>(Class);
FClass2 ClassMove2 = std::move(Class);

동일하게 작동한다.

std::move 된 객체를 함수에 전달한다면,
우측값 레퍼런스를 인자로 받는 함수 ex) 이동 생성자, 이동 대입 연산자
가 오버로딩 되어서 선택된다.

만약 이동 생성자가 구현이 안 되어있다면 복사생성자가 호출된다.

또한 이동생성자는 noexcept키워드를 붙여야 stl 컨테이너 ex) vector에서 호출한다.

이동을 하게되면 기존 객체에서 소유권을 잃게되는데 이동 중 예외가 호출되면
문제가 생기기 때문에 noexcept키워드가 달리지 않은 이동생성자는 호출하지 않는다.

상속시 생성 순서

FDerived 클래스가 FBase클래스를 상속받고 있을 때,

  1. FDerived클래스의 생성자 진입
  2. FBase클래스의 생성자 진입
  3. FBase클래스의 초기화 구문 실행
  4. FBase클래스의 생성자 실행
  5. FDerived클래스의 초기화 구문 실행
  6. FDerived클래스의 생성자 실행

이 순서로 실행된다.

소멸자는
1. FDerived클래스의 소멸자
2. FBase클래스의 소멸자
이 순서이다.

가상함수테이블

c++은 클래스에 가상함수가 존재하면 가상함수를 처리하기 위해 가상함수 테이블을 생성한다.
배열과 동일하게 가상함수 배열을 가리키는 포인터가 존재한다.

자식 클래스의 가상테이블은 기본적으로 부모의 가상테이블을 그대로 가져오며,
오버라이딩된 함수만 해당 함수의 주소를 업데이트 시켜준다.
부모 클래스에 없는 자식 클래스의 가상 함수는 기존 가상테이블에 덧붙인다.

가상함수가 아닌 일반함수는 저장되지 않는다.

소멸자에 virtual을 두는 이유

FDerivedVirtual* Instance = new FDerivedVirtual;
FBaseVirtual* Instance2 = Instance;
delete Instance2;

이런 식으로 자식 클래스를 부모클래스에 할당하는 경우 (Upcasting)
부모클래스의 소멸자에 virtual이 있다면 알아서 자식 클래스의 소멸자를 호출하게 된다.

하지만 소멸자에 virtual이 없다면 무조건 해당 시점의 클래스의 소멸자를 호출하므로
위와 같은 경우, FBaseVirtual클래스의 소멸자를 호출하게 된다.
따라서 원래 생성된 자식클래스인 FDerivedVirtual의 소멸자가 호출이 안 되어 메모리 누수가 일어날 수 있다.

Final키워드

virtual void Method() override final
{}

final키워드를 가상함수에 선언하게 되면 해당 가상함수는 더 이상 override를 할 수 없게된다.

멤버함수 뿐만 아니라 클래스에도 선언할 수 있다.

		class FC final : public FB
		{
		public:
			FC()
			{
				// this->vtPointer = C의VTable주소
			}
			virtual ~FC() {}
		};
profile
코딩 창고!

0개의 댓글