C++의 ref 한정자가 무엇인지, 멤버 함수에서 lvalue/rvalue 문맥에 따라 어떻게 동작하는지, 그리고 fluent interface나 변환 제어 같은 활용 사례를 간단한 예제로 살펴봅니다.
published at 21.05.2026 16:28 by Jens Weller
Save to InstapaperPocket
ref 한정자는 오늘날에는 오래된 C++11 기능이지만, 최근 저는 이것에 대해 더 알고 싶었습니다. 특히 잠재적인 사용 사례가 궁금했습니다.
이 기능의 특징적인 점이 바로 그것입니다. 예제는 본 적이 있지만, 설득력 있는 사용 사례가 함께 제시되는 경우는 드물었습니다. 이 기능은 C++에서 매우 구체적인 일을 해내는 훌륭한 방법입니다. 멤버 함수에 일종의 미세 조정을 더해 rvalue 문맥에서 유용한 특수화를 만들 수 있습니다. 좋게 들리지 않나요?
ref 한정자는 멤버 함수에만 허용되는데, 이는 const 한정자와 비슷하게 해당 값 문맥에서 이 멤버 함수를 선택하도록 한정하기 때문입니다:
struct refqualified{
mytype var;
const mytype& getVar()const&{return var;}
void setVar(const mytype& v)&{var=v;}
mytype getVar()&&{return var;}
};
이 간단한 예제에서 클래스는 멤버 변수를 하나 보관합니다. getter는 const 참조를 반환하므로, 임시 객체/rvalue 문맥에서 getVar()를 호출하면 그 참조가 lvalue이기 때문에 컴파일 오류가 발생합니다. setter도 비슷합니다. 이 설계에서는 rvalue 문맥이 아닌 경우에만 setVar를 호출할 수 있도록 하는 것이 목표입니다. rvalue ref 한정 getter는 변수의 복사본을 반환하며, 이것은 move가 될 수도 있습니다. ref 한정자는 =delete와 결합할 수도 있는데, 그러면 이 문맥에서 호출될 때 컴파일 오류가 발생합니다.
그러므로 C++의 ref 한정자는 다음과 같습니다:
이것들은 ref 한정자가 없는 동일한 시그니처의 멤버 함수와 함께 조합할 수 있으므로, rvalue를 처리하기 위해 && 한정 멤버 함수를 추가하면서 const& 및 & ref 한정 호출에 대한 오버로드는 제공하지 않을 수 있습니다. ref 한정자는 생성자와 소멸자에는 적용되지 않습니다. 이는 lvalue/rvalue 문맥에 대한 특수화를 가능하게 하므로, 변환을 허용하거나 f()&&=delete;로 방지하는 데 사용할 수 있습니다.
ref 한정자의 또 다른 잠재적 사용 사례는 다양한 멤버를 설정하고/또는 함수를 호출하기 위한 fluent interface를 허용하되, 인스턴스가 변수에 대입되기 전까지만 가능하게 하는 것입니다: auto l = createLogger().setSink("mysink").setDebug(true);
간단한 구현은 다음과 같을 수 있습니다:
enum loglevel{NONE,SOME,FULL};
struct Logger
{
Logger(){};//default values
loglevel level=loglevel::NONE;
std::string sink="none";
bool debug=false;
Logger&& setLogLevel(loglevel ll)&&{level=ll;return std::move(* this);}
Logger&& setSink(std::string s)&&{sink = s;return std::move(*this);}
Logger&& setDebug(bool d = true)&&{debug=d;return std::move(*this);}
};
이것은 builder 디자인 패턴으로 확장될 수 있으며, 여기서는 특정 클래스가 해당 타입의 객체를 생성하는 작업을 맡습니다. 하지만 저는 단순하게 유지하려 했기 때문에 constexpr나 noexcept는 넣지 않았습니다. 이 문맥에서는 그런 것들도 의미가 있을 수 있습니다. 이것은 기본 생성자를 가진 예시 “logger” 클래스이지만, setter 멤버 함수의 체이닝 호출로 로깅 객체를 명시적으로 구성할 수 있는 선택지를 제공하고자 합니다. 이는 java에서 잘 알려진 패턴이지만, 몇몇 인기 있는 C++ 라이브러리에서도 이 fluent interface가 사용되는 것을 본 적이 있습니다. 이제 ref 한정자를 사용하면 rvalue가 아닌 참조에서 설정 함수들을 호출하는 것을 컴파일 오류로 만들 수 있습니다. 물론 setter를 & ref 한정자로도 구현하여 rvalue를 처리하고 참조를 반환하게 할 수도 있습니다.
사용법:
Logger l = Logger().setSink("mysink").setDebug();
//l.setLogLevel(loglevel::SOME);//error
std::println("sink:{}, debug:{}",l.sink,l.debug);
이것은 C++에서 완벽한 builder 패턴 구현이라기보다는 ref 한정자를 보여주기 위한 예제에 가깝습니다. 코드는 Compiler Explorer에서 확인할 수 있습니다. fluent interface를 사용하는 builder 패턴에 대한 더 깊이 있는 논의는 stack overflow에서 찾을 수 있습니다.
ref 한정자에 대한 심층 기사에서는 자원을 보유한 클래스가 lvalue/rvalue 문맥에 따라 자원을 move하거나 copy하는 변환에서의 사용을 언급합니다.
Andreas Fertig는 자신의 블로그에서 범위 기반 for 루프처럼 객체의 일부를 반환하는 임시 객체를 처리할 수 있게 해준다고 지적합니다. && 한정 멤버 함수의 반환값에 std::move를 사용하는 것은 복사를 방지하므로 유용합니다.
Meeting C++ patreon community에 참여하세요!
Meeting C++의 이 글과 다른 게시물들은 patreon의 후원자분들 덕분에 가능했습니다!