[Advanced C++] 28. reference/address return, Input/Output Parameter, auto with pointer & reference, std::optional

dev.kelvin·2025년 3월 18일
1

Advanced C++

목록 보기
28/74
post-thumbnail

1. Return by Reference & Address

참조 반환 및 주소 반환

함수의 인자로 기본 타입과 같은 경우에는 복사가 일어나도 코스트가 크지 않지만 클래스나 구조체와 같은 경우에는 상황에 따라 코스트가 굉장히 크게 드는 경우가 발생할 수 있다, 따라서 인자로 넘길때는 참조나 주소 전달 방식으로 전달하는게 좋다

이는 반환할때도 마찬가지이다, 값 타입으로 반환하게 되면 함수가 반환값을 복사한 후 호출자에게 전달한다 따라서 인자로 넘길때와 마찬가지로 코스트가 많이 들 수 있다

	class Knight()
    {
    };
    
    Knight returnKnight(); //return 시 클래스 복사가 일어나기 때문에 코스트 상승

따라서 함수에서 값을 반환할때도 타입을 참조나 포인터 타입으로 반환하여 불필요한 복사를 방지할 수 있다

	std::string& returnString(); //&반환
    const std::string& returnConstString(); //const& 반환

참조 반환은 말그대로 return하는 객체에 대한 참조를 반환하는 것이기 때문에 복사 비용이 발생하지 않는다

참조 반환 주의사항

return하는 객체에 대한 참조를 반환하는것이기 때문에 함수가 종료되어도 해당 객체는 valid해야 한다

만약 소멸되는 객체에 대한 참조를 return하면 dangling 참조가 되고 이를 사용하면 undefined behavior가 발생할 수 있다 (일반 로컬변수에 대한 참조 return이 대표적인 문제)

	const std::string& getMyName()
    {
        static const std::string Name { "Kelvin" }; //static정적 변수이기 때문에 함수가 종료되어도 객체가 소멸되지 않는다, 따라서 참조 반환이 가능 (그러나 예시를 위해서이고 static을 참조하는건 좋은 방식은 아니라고 생각함 -> 정적변수 참조로 의도치 않은 정적변수 제어가 일어날 수 있기 때문)
        return Name;
    }
    
	const std::string& getMyName()
    {
        const std::string Name { "Kelvin" }; //로컬변수이기 때문에 함수가 종료되면 객체가 같이 소멸된다, 따라서 참조 반환으로 사용하면 문제가 발생한다
        return Name;
    }

추가로 함수의 참조 반환값을 참조타입 변수에 받아 사용해도 dangling 참조가 될 수 있으며 undefined behavior를 일으킬 수 있다, 하지만 함수로부터 return받은 참조값이 아닌 직접 바인딩 방식으로는 임시객체의 수명이 연장되어 안전한 참조 사용이 가능하다

	const int& returnNum()
    {
    	return 5;
    }
    
    int main()
    {
    	const int& ref{ returnNum() }; //returnNum()으로부터 return된 5는 임시객체이다, 다음line에서 사라진다 (임시객체 수명 연장X)
        
        std::cout << ref; //dangling reference!
    }
    
    int main()
    {
    	const int& ref{ 5 }; //직접 바인딩으로 임시객체 수명 연장O, 안전한 참조
        
        std::cout << ref;
    }

주소 반환도 참조 반환과 거의 동일하게 함수가 반환값을 return할 때 복사가 일어나지 않고 주소를 return하는 방식이다 (포인터 return)

	Knight* getKnight();

포인터 return이기 때문에 받고 nullptr check가 꼭 들어가야 한다


2. In/Out Parameters

입/출력 매개변수

일반적으로 함수에 인자를 전달하여 해당 인자를 입력만 받고 수정하지 않는 매개변수를 입력 매개변수라고 한다

	int foo(int a) //입력 매개변수
    {
    }
    
    foo(100);

출력 매개변수란 non-const reference나 non-const pointer를 전달하여 함수의 내부에서 함수 호출시 넘긴 인자의 값을 변경하기 위한 목적의 매개변수이다

함수의 반환값 이외에도 출력 매개변수로 데이터를 return할 수 있게된다

	void foo(int& a)
    {
    	a = 200;
    }
    
    int main()
    {
    	int i{ 100 };
        foo(i);
        
        std::cout << i << '\n'; //200, non-const reference 전달 방식으로 원본 변경 후 output
    }

보통 일반적으로 output 매개변수는 오른쪽 끝에 배치한다

출력 매개변수는 일반적인 return 방식보다 조금 덜 직관적일 수 있다 (return value가 있는 함수는 그냥 그대로 사용하면 되지만 출력 매개변수 방식은 값을 넣고 함수에 넣고 호출한 후 설정된 값을 사용하는 방식이기 때문)

그리고 출력 매개변수에 &를 사용하게 되면 일반 변수를 집어 넣는것과 거의 동일하기 때문에 output용도인지 확인하기가 어렵다 (코드를 잘 확인해야함)


3. pointer, reference auto type

pointer와 reference에서의 auto

auto는 컴파일러가 초기화값으로부터 타입을 추론하게 하는 키워드이다

추가로 auto는 const, constexpr을 제거한다, 따라서 const 타입을 auto로 처리했다면 명시적으로 const를 붙여주는게 좋다

	const int i{ 10 };
    const auto constInt{ i };

auto는 const뿐 아니라 참조도 제거한다

	std::string& foo();
    
    auto refString{ foo() }; //std::string으로 컴파일러가 추론함

const와 마찬가지로 auto에 &를 명시적으로 붙여야 참조타입이 된다 (포인터는 사라지지 않음)

	auto& refString{ foo() };

top/low level const

top level const란 변수를 직접 제한하는 const를 의미한다

	const int a;
    int* const ptr;

low level const란 참조나 포인터를 통해 접근하는 대상에 적용되는 const를 의미한다

	const int* ptr;
    const int& ref;

타입추론 시 포인터는 제거되지 않는다, 다만 auto*로 포인터 타입이라는걸 명시하는게 좋다


4. std::optional

std::optional

C++17부터 std::optional이 도입되었다, 이를 활용하여 함수가 값을 반환하거나, 반환하지 않을 수도 있는 케이스를 명확히 표현이 가능하다

	#include <optional>
    
    std::optional<int> optionalFunc(int a)
    {
    	if(a == 0)
        {
        	return std::nullopt; //함수가 optional null을 return하게 된다, 아니면 {}로도 가능
        }
        
        return a;
    }
    
    std::optional<int> test = optionalFunc(0);
    if (test) //optional값이 null인지 아닌지 체크가 가능해진다
    {
    }
    else
    {
    }
    
    if (test.has_value()) //이런식으로 값이 있는지 확인도 가능하다
    
    *test; //역참조 연산자를 이용하여 값을 가져올 수 있다
    test.value(); //.value()로 값을 가져올 수 있다
    test.value_or(값); //값이 없으면 ()에 넣은 값을 반환

마찬가지로 std::nullopt를 null체크하지 않고 그대로 사용하게 되면 크래시가 발생한다, 꼭 nullptr체크나 has_value()로 값이 valid한지 체크하고 사용해야 한다

결론적으로 return타입이 std::optional< type >이면 해당 함수는 값을 반환할 수도 안할수도 있다

물론 함수의 인자로도 사용이 가능하다

	void foo(std::optional<const int> num = std::nullopt)
    {
    	if (num.has_value())
        {
        }
        else
        {
        }
    }
    
    foo(); //매개변수 기본값 std::nullopt로 들어가 else구문 실행
    foo(10); //value가 있기 때문에 if구문 실행
profile
GameDeveloper🎮 Dev C++, DataStructure, Algorithm, UE5, Assembly🛠, Git/Perforce🌏

0개의 댓글