RVO는 return value Optimization이고 Nrvo는 named return value optimization이다.
컴파일러가 최적화하는 동작으로 함수에서 반환할 객체가 있을 때 동작한다.
Nrvo는 반환할 객체를 새로 선언 후 반환해주는 방식이고,
RVO는 반환할 객체를 별다른 선언없이 바로 return 해주는 식이다.
예를들어 아래와 같은 구조체가 있고,
struct FParam
{
FParam();
FParam(const FParam& InOther);
~FParam();
void operator=(const FParam& InOther);
int Value[1000]{};
int A = 999;
};
아래와 같은 함수들이 있다고 하자
(NRVO 방식)
FParam CallByValue(FParam InParam)
{
FParam ret;
return ret;
}
받은 인자 그대로 return
FParam CallByValue2(FParam InValue)
{
return InValue;
}
(RVO 방식)
FParam CallByValue3(FParam InValue)
{
return FParam();
}
RVO나 NRVO가 동작할 때, 반환시 임시객체를 호출하는 과정이 생략된다.
FParam bullet;
FParam bullet1 = CallByValue(bullet);
이렇게 코드를 짜봤다. 생각대로라면
1. bullet 생성자 호출,
2. CallByValue의 인자로 bullet이 들어가는 과정에서 복사 생성자 호출,
3. 내부에서 반환할 FPAram ret의 생성자 호출,
4. 반환할 ret을 임시로 저장할 임시객체의 복사생성자 호출,
5. 함수가 끝나며 ret의 소멸자 호출,
6. 대입연산자가 호출되며 bullet1에 임시객체가 들어가게 되고,
7. 임시객체의 소멸자가 호출
이 흐름일거라고 생각했고 실행해 본 결과

이런식으로 bullet 생성자 호출, 인자로 넘어가며 복사 생성자 호출,
내부에서 반환할 FParam ret의 생성자 호출까진 같다.
하지만 임시객체의 복사생성자부터 대입연산자까지 호출되지 않는다.
신기해서 이런 저런 실험을 해보던 중 어쩔때는 임시객체가 생성되고,
어쩔때는 대입연산자까지 호출이 되는 경우를 발견했다.
바로 자기자신을 대입하거나,
FParam bullet;
bullet = CallByValue(bullet);
인자로 넘어온 객체를 그대로 반환하는 경우였다.
FParam CallByValue2(FParam InValue)
{
return InValue;
}
반환값을 인자로 넣은 객체에 다시 넣을 때(a), 안 넣을 때 (b)
*
함수 인자로 들어온값을 그대로 반환할 때(1), RVO 방식일때(2), NRVO방식일 때(3)
이렇게 6가지다.






보면 RVO나 NRVO나 같게 나오고
인수로 넣은 객체에 반환값을 다시 집어넣는 경우(a)
대입연산자가 호출이 된다.
또한 함수의 인자를 바로 반환하는 경우(1),
임시객체가 만들어져 복사생성자가 호출되는 것으로 보이고,
RVO나 NRVO방식을 사용하면 새로 선언하는 객체의 생성자만 호출되는 것으로 보인다.
아 다 올리고 생각난건데 대입 연산자 FParam Operator = 하고 "\n"을 안해줘서
FParam Operator = ~FParam이렇게 표시된것이다..
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
_CrtSetDbgFlag함수는 디버그창에 누수가 나면 띄워주는 용도이고,
저 플래그들을 사용하면 보고에 누수 지점의 번호가 출력된다.
_CRTDBG_LEAK_CHECK_DF 플래그는
On : 프로그램 종료 시 자동 누수 검사 수행하고 애플리케이션이 할당된 모든 메모리를 해제하지 못한 경우 오류 보고서를 생성합니다
_CRTDBG_ALLOC_MEM_DF 플래그는
ON: 디버그 힙 할당과 메모리 블록 형식 식별자(예: _CLIENT_BLOCK)의 사용을 설정합니다. OFF: 힙의 연결된 목록에 새 할당을 추가하지만 블록 형식을 .로 _IGNORE_BLOCK설정합니다.
_crtBreakAlloc
위 함수에 _CrtSetDbgFlag함수에서 출력한 블록 번호를 인자로 넣으면
누수되는 지역에서 디버깅이 멈춘다.
struct FStruct
{
//FStruct() :{}
int a = 0;
int b{ 10 };
int c = 20;
};
이런 구조체가 있을 때, 구조체의 생성자가 없다면
FStruct Instance2{ .a = 10 };
FStruct Instance3{ .b = 20, .c = 100 };
이런식으로 초기화가 가능하다.
하지만 생성자가 있다면 사용 못한다.
int** DoublePointer{nullptr};
이런 식으로 이차원 포인터가 있을 때 할당하려면
먼저 이차원포인터를 우선 할당한다.
이차원 포인터는 포인터를 가리키므로 new int* 이런식으로 포인터를 할당해준다.
DoublePointer = new int*;
그 후 이차원 포인터가 가리키는 포인터를 할당해주면 된다.
*DoublePointer = new int{ 10 };
해제할 경우는 가리키고 있는 포인터를 먼저 해제해준 후 자신을 해제 시켜야한다.
delete* DoublePointer;
delete DoublePointer;
DoublePointer = nullptr;
2차원 포인터는 2차원 배열로 사용할 수도 있지만,
함수를 통해 포인터를 할당시키는 데 사용하기도 한다.
int* Pointer=nullptr;
void MemAlloc(int* ptr)
{
ptr = new int{10};
}
위 Pointer을 MemAlloc(Pointer); 이렇게 호출하면 할당이 되는가?
아니다.
Pointer가 가리키는 주소만 넘어가고 해당 주소를 가리키는 포인터 ptr이 할당되었을 뿐
기존 Pointer는 할당되지 않았다.
여기서 할당시키려면 Pointer를 가리키는 포인터를 넣어줘야 한다.
이차원포인터다!
void AllocateCorrect(int** InDoublePointer)
{
*InDoublePointer = new int{ 10 };
}
위 함수에 AllocateCorrect(&Pointer) 이런식으로 포인터의 주소를 넣고,
해당 이차원포인터가 가리키는 포인터가 바로 Pointer이므로 할당시켜주면 정상적으로 할당된다.
해제도 마찬가지다.
함수에 포인터 주소값 넣어서 다른 포인터가 가리키게 하고 다른 포인터를 해제시키면 안된다!
이차원 포인터를 이용해 해제시켜야한다.