C++ 임시 객체: 생성·소멸 시점, const 참조 수명 연장 규칙, 댕글링 참조
이 글의 핵심
임시 객체는 보통 전체 표현식이 끝나면 사라지기 때문에, 참조로 잡아 둔 값이 다음 줄에서 이미 소멸된 상태인 버그가 자주 생깁니다. 이 글은 수명 연장이 적용되는 경우와 적용되지 않는 경우, 비const 참조에 임시 객체를 넘길 수 없는 이유, 복사·이동 생성자 호출을 추적해 불필요한 임시 객체를 찾는 법을 설명합니다.
임시 객체란?
임시 객체(temporary objects) 는 표현식 평가 중에 생성되는 이름 없는 객체입니다. 일반적으로 표현식이 끝나면 즉시 소멸되지만, const 레퍼런스나 우측값 레퍼런스로 바인딩하면 수명이 연장됩니다.
std::string s = std::string("Hello"); // 임시 객체 생성
// std::string("Hello")는 임시 객체
int x = 1 + 2; // 3은 임시 값
왜 중요한가?:
- 성능: 불필요한 임시 객체는 성능 저하
- 안전성: 임시 객체의 댕글링 레퍼런스 방지
- 최적화: RVO, NRVO, 이동 의미론 이해
임시 객체 vs 일반 객체:
| 특징 | 일반 객체 | 임시 객체 |
|---|---|---|
| 이름 | ✅ 있음 | ❌ 없음 |
| 수명 | 스코프 끝 | 표현식 끝 |
| 수명 연장 | - | ✅ const 레퍼런스 |
| 이동 가능 | ✅ | ✅ |
| 복사 가능 | ✅ | ✅ |
// 일반 객체
std::string name("Alice"); // 스코프 끝까지 유효
// 임시 객체
std::string("Alice"); // 표현식 끝에 소멸
임시 객체 생성 시점
// 1. 함수 반환
std::string getName() {
return "Alice"; // 임시 객체
}
// 2. 타입 변환
void func(std::string s) {}
func("Hello"); // const char* -> std::string 임시 객체
// 3. 연산자
std::string s = "Hello" + std::string(" World"); // 임시 객체
// 4. 명시적 생성
Widget(10); // 임시 객체
C++17부터는 용어가 조금 더 정밀해졌습니다. getName()이나 std::string("Hello") 같은 표현식은 prvalue라고 부르며, prvalue는 그 자체로는 아직 객체가 아니라 “객체를 초기화하는 방법”에 가깝습니다. 실제 임시 객체는 prvalue를 참조에 바인딩하거나, 멤버에 접근하거나, 버려질 때처럼 메모리가 필요한 순간에 만들어집니다(temporary materialization). 그래서 std::string s = std::string("Hello");는 C++17부터 임시 객체를 만든 뒤 복사·이동하는 것이 아니라, s 자체를 바로 초기화하도록 보장됩니다. 복사·이동 생성자가 삭제된 타입도 이런 코드가 컴파일되는 이유입니다.
반면 2번과 3번은 여전히 진짜 임시 객체가 생깁니다. func("Hello")는 const char*에서 std::string으로의 변환 결과가 매개변수를 초기화하고, "Hello" + std::string(" World")는 std::string(" World") 임시와 operator+의 결과가 만들어집니다. 긴 문자열 연결 a + b + c + d는 중간 결과마다 임시 문자열을 만들고 버리는데, C++11 이후에는 operator+의 우측값 오버로드가 임시의 버퍼를 재사용해 비용이 많이 줄었습니다.
임시 객체 수명
class Temp {
public:
Temp(int val) : value(val) {
std::cout << "생성: " << value << std::endl;
}
~Temp() {
std::cout << "소멸: " << value << std::endl;
}
private:
int value;
};
void func() {
Temp(10); // 즉시 소멸
std::cout << "다음 줄" << std::endl;
}
Temp(10);처럼 이름 없이 만든 객체가 그 줄에서 바로 소멸한다는 것은 RAII 가드에서 치명적인 버그가 됩니다. 대표적인 예가 std::lock_guard<std::mutex>{mtx};입니다. 의도는 스코프 끝까지 락을 잡는 것이지만, 이름을 주지 않았으므로 임시 객체가 되어 세미콜론에서 바로 락이 풀리고, 그 아래 코드는 락 없이 실행됩니다. 컴파일 에러도 없어서 데이터 레이스로 뒤늦게 발견됩니다(소괄호로 std::lock_guard<std::mutex>(mtx);라고 쓰면 구문상 mtx라는 이름의 변수 선언으로 해석되어 전혀 다른 컴파일 에러가 납니다). 가드는 반드시 std::lock_guard<std::mutex> lock(mtx);처럼 이름을 붙여야 합니다. [[nodiscard]]를 생성자에 붙이면(C++20) 이런 이름 없는 생성에 컴파일러 경고를 받을 수 있습니다.
임시 객체 수명 규칙:
- 기본 규칙: 임시 객체는 전체 표현식(full expression) 이 끝나면 소멸됩니다.
#include <iostream>
class Temp {
public:
Temp(int v) : value(v) {
std::cout << "생성 " << value << '\n';
}
~Temp() {
std::cout << "소멸 " << value << '\n';
}
int get() const { return value; }
private:
int value;
};
int main() {
std::cout << "=== 시작 ===\n";
int x = Temp(10).get(); // 표현식 끝에 소멸
std::cout << "=== 끝 ===\n";
}
// 출력:
// === 시작 ===
// 생성 10
// 소멸 10
// === 끝 ===
“전체 표현식”은 보통 세미콜론까지의 한 문장이라고 생각하면 됩니다. 이 규칙 덕분에 Temp(10).get()처럼 임시 객체의 멤버 함수를 호출하고 결과를 쓰는 것은 안전합니다. get()이 끝날 때까지 임시 객체가 살아 있고, 반환된 int는 값으로 복사되어 x에 들어가기 때문입니다. 문제는 반환값이 값이 아니라 참조나 포인터일 때입니다. 그 참조가 가리키는 대상이 임시 객체 안에 있다면, 세미콜론이 지나는 순간 가리킬 대상이 사라집니다. 이 글의 댕글링 예제는 전부 이 한 가지 패턴의 변형입니다.
- 수명 연장: const 레퍼런스 또는 우측값 레퍼런스로 바인딩하면 수명이 연장됩니다.
int main() {
std::cout << "=== 시작 ===\n";
const Temp& ref = Temp(10); // 수명 연장
std::cout << "중간\n";
std::cout << ref.get() << '\n';
std::cout << "=== 끝 ===\n";
}
// 출력:
// === 시작 ===
// 생성 10
// 중간
// 10
// === 끝 ===
// 소멸 10
수명 연장은 참조가 임시 객체에 직접 바인딩될 때 한 번만 일어나며, 연장된 임시는 그 참조 변수가 스코프를 벗어날 때 소멸합니다. 연장 규칙은 편리하지만 “이 참조가 임시를 가리킨다”는 사실이 코드에 드러나지 않아 읽는 사람을 헷갈리게 하므로, 실무에서는 const Temp& ref = Temp(10); 대신 그냥 Temp t(10);이나 auto t = Temp(10);으로 값을 받는 편이 명확합니다. C++17 이후에는 값으로 받아도 추가 복사가 없습니다.
- 수명 연장 예외: 멤버 함수가 반환한 참조나, 함수 인자를 거쳐 돌아온 참조는 수명을 연장하지 않습니다.
struct Inner {
int value = 42;
};
struct Outer {
Inner inner;
const Inner& getInner() const { return inner; }
};
Outer getOuter() {
return Outer();
}
int main() {
// ✅ 데이터 멤버에 직접 접근: 임시 Outer 전체의 수명이 연장됨
const Inner& inner = getOuter().inner;
// ❌ 댕글링 레퍼런스: 멤버 함수가 반환한 참조는 연장되지 않음
const Inner& inner3 = getOuter().getInner();
// getOuter()의 임시 객체는 문장 끝에서 소멸
// inner3은 댕글링
// ✅ 전체 객체 저장
const Outer& outer = getOuter();
const Inner& inner2 = outer.inner; // 안전
}
이 부분은 흔히 거꾸로 알려져 있어서 정확히 짚겠습니다. getOuter().inner처럼 데이터 멤버에 직접 접근하는 식에 참조를 바인딩하면, 표준은 멤버가 속한 임시 객체 전체의 수명을 연장합니다. 컴파일러가 참조가 임시의 일부를 가리킨다는 것을 식의 모양만 보고 알 수 있기 때문입니다. 반면 getOuter().getInner()는 함수 호출이라, 컴파일러 입장에서는 반환된 참조가 임시 안을 가리키는지, 전역 변수를 가리키는지 알 방법이 없습니다. 그래서 연장이 일어나지 않고 inner3은 문장이 끝나자마자 소멸된 객체를 가리킵니다. std::optional의 value(), std::map의 at(), 게터 함수가 모두 이 경우에 해당해 const auto& v = makeOptional().value(); 같은 코드가 대표적인 댕글링 버그가 됩니다.
같은 이유로 const std::string& s = std::max(a, b + "x");처럼 참조를 받아 참조를 반환하는 함수에 임시를 넘기는 것도 위험합니다. std::max는 인자 중 하나의 참조를 그대로 돌려주므로, 임시 쪽이 선택되면 문장 끝에서 사라집니다. 이런 버그는 AddressSanitizer의 “stack-use-after-scope”나 “heap-use-after-free” 보고로 가장 확실하게 잡을 수 있고, GCC 13부터는 -Wdangling-reference 경고가 일부 경우를 컴파일 단계에서 알려 줍니다.
실전 예시
예시 1: 수명 연장
#include <string>
std::string getName() {
return "Alice";
}
int main() {
// ❌ 즉시 소멸
const char* ptr = getName().c_str();
// getName()의 임시 객체 소멸
// ptr은 댕글링
// ✅ 수명 연장
const std::string& name = getName();
const char* ptr2 = name.c_str(); // 안전
// name이 스코프 벗어날 때까지 유효
}
getName().c_str()은 실무에서 가장 흔한 임시 객체 버그입니다. c_str()이 돌려준 포인터는 문자열 내부 버퍼를 가리키는데, 그 문자열이 임시라 문장 끝에서 해제됩니다. 짧은 문자열은 SSO(작은 문자열 최적화) 때문에 버퍼가 스택에 있어 한동안 원래 값이 그대로 보이는 경우가 많고, 그래서 테스트에서는 멀쩡하다가 긴 문자열이 들어오는 운영 환경에서만 깨진 문자가 출력되곤 합니다. 저도 C API에 경로 문자열을 넘기는 코드에서 이 버그를 겪었는데, 증상이 “가끔 파일을 못 찾는다”여서 원인을 찾는 데 시간이 오래 걸렸습니다. 반대로 printf("%s", getName().c_str());처럼 같은 문장 안에서 포인터를 다 쓰는 것은 안전합니다. 임시가 문장 끝까지 살아 있기 때문입니다. 포인터를 변수에 저장하는 순간이 위험 신호입니다.
예시 2: 함수 인자
#include <iostream>
class Widget {
public:
Widget(int val) : value(val) {
std::cout << "Widget 생성: " << value << std::endl;
}
~Widget() {
std::cout << "Widget 소멸: " << value << std::endl;
}
int getValue() const {
return value;
}
private:
int value;
};
void process(const Widget& w) {
std::cout << "처리: " << w.getValue() << std::endl;
}
int main() {
process(Widget(10)); // 임시 객체
// process 호출 후 소멸
}
함수 인자로 넘긴 임시는 함수 호출을 포함한 전체 문장이 끝날 때까지 살아 있으므로, process 안에서 w를 쓰는 것은 안전합니다. 위험해지는 것은 함수가 그 참조를 어딘가에 저장할 때입니다. 예를 들어 클래스 생성자가 const Widget&를 받아 멤버 참조로 보관하면, Holder h(Widget(10)); 이후 h가 가진 참조는 이미 소멸된 객체를 가리킵니다. 참조 멤버에 임시를 바인딩하는 것은 수명을 연장하지 않습니다. 그래서 받은 값을 보관해야 하는 생성자라면 참조가 아니라 값으로 받아 이동(Holder(Widget w) : w_(std::move(w)) {})하는 것이 안전한 기본값입니다.
예시 3: 반환값 최적화
#include <vector>
std::vector<int> createVector(size_t size) {
std::vector<int> result(size);
for (size_t i = 0; i < size; i++) {
result[i] = i;
}
return result; // 임시 객체 (RVO)
}
int main() {
auto vec = createVector(1000);
// RVO로 복사 없음
}
예시 4: 연산자 오버로딩
class Vector2 {
private:
float x, y;
public:
Vector2(float x, float y) : x(x), y(y) {}
Vector2 operator+(const Vector2& other) const {
return Vector2(x + other.x, y + other.y); // 임시 객체
}
Vector2 operator*(float scalar) const {
return Vector2(x * scalar, y * scalar); // 임시 객체
}
};
int main() {
Vector2 v1(1, 2);
Vector2 v2(3, 4);
Vector2 v3 = v1 + v2; // 임시 객체 생성
Vector2 v4 = v1 * 2.0f; // 임시 객체 생성
}
임시 객체 최적화
// RVO (Return Value Optimization)
std::string func1() {
return std::string("Hello"); // 임시 객체 생략
}
// NRVO (Named RVO)
std::string func2() {
std::string result = "Hello";
return result; // 복사 생략
}
// 이동 의미론
std::string func3() {
std::string result = "Hello";
return result; // 이동 (복사 생략 안될 때)
}
세 함수는 보장 수준이 다릅니다. func1처럼 prvalue를 바로 반환하는 경우는 C++17부터 복사 생략이 보장되어 호출자의 변수가 직접 초기화됩니다. func2·func3처럼 이름 있는 지역 변수를 반환하는 NRVO는 허용되지만 보장되지는 않는 최적화로, 대부분의 컴파일러가 최적화 빌드에서 적용하지만 분기마다 다른 변수를 반환하는 등 복잡한 경우에는 생략되지 않습니다. 그때도 지역 변수는 반환될 때 자동으로 우측값으로 취급되어 이동 생성자가 호출되므로 깊은 복사는 일어나지 않습니다.
그래서 return std::move(result);는 오히려 해롭습니다. std::move를 붙이면 반환 식이 더 이상 “지역 변수 이름”이 아니게 되어 NRVO 대상에서 빠지고, 생략될 수 있었던 이동이 반드시 한 번 실행됩니다. Clang과 GCC 9+는 이 경우 “moving a local object in a return statement prevents copy elision”(-Wpessimizing-move) 경고를 냅니다. 자세한 조건은 RVO/NRVO 글에서 다룹니다.
자주 발생하는 문제
문제 1: 댕글링 레퍼런스
// ❌ 임시 객체 즉시 소멸
std::string getName() {
return "Alice";
}
const char* ptr = getName().c_str();
// getName()의 임시 객체 소멸
// ptr은 댕글링
// ✅ 수명 연장
const std::string& name = getName();
const char* ptr = name.c_str();
문제 2: 비const 레퍼런스
// ❌ 임시 객체는 비const 레퍼런스 불가
void func(std::string& s) {}
// func("Hello"); // 에러
// ✅ const 레퍼런스 또는 우측값 레퍼런스
void func1(const std::string& s) {}
void func2(std::string&& s) {}
func1("Hello"); // OK
func2("Hello"); // OK
비const 참조에 임시를 바인딩할 수 없는 이유는 수정이 사라지기 때문입니다. func("Hello")가 허용된다면 함수는 const char*에서 변환된 임시 std::string을 수정하고, 그 임시는 문장이 끝나면 버려지므로 호출자는 수정 결과를 볼 수 없습니다. 호출자가 “내 변수가 바뀌었을 것”이라고 착각하는 버그를 막으려고 언어가 금지한 것입니다. GCC는 “cannot bind non-const lvalue reference of type ‘std::string&’ to an rvalue of type ‘std::string’” 에러를 냅니다. 참고로 MSVC는 오래전부터 이를 확장으로 허용해 왔는데, /permissive-(최신 프로젝트 기본값)에서는 표준대로 에러가 나므로 MSVC에서만 컴파일되던 코드가 다른 컴파일러에서 깨지는 원인이 되기도 합니다.
const std::string&는 “읽기만 하겠다”는 약속이라 임시를 받아도 문제가 없고, std::string&&는 “버려질 값을 받아 내가 가져가겠다”는 뜻이라 오히려 임시를 위해 만들어진 타입입니다. 둘 다 오버로드해 두면 호출자가 넘긴 것이 임시인지 아닌지에 따라 복사와 이동을 나눠 처리할 수 있습니다.
문제 3: 멤버 접근
class Data {
public:
int getValue() const {
return value;
}
private:
int value = 42;
};
Data getData() {
return Data();
}
int main() {
// ✅ 임시 객체 멤버 접근
int x = getData().getValue(); // OK
// ❌ 포인터 저장
// const Data* ptr = &getData(); // 에러
}
문제 4: 컨테이너 임시 객체
#include <vector>
std::vector<int> getVector() {
return {1, 2, 3};
}
int main() {
// ❌ 반복자 저장
// auto it = getVector().begin(); // 위험
// ✅ 컨테이너 저장
auto vec = getVector();
auto it = vec.begin(); // 안전
}
반복자는 컨테이너 내부를 가리키는 일종의 포인터라서, 임시 컨테이너에서 얻은 반복자는 c_str() 포인터와 똑같이 문장 끝에서 댕글링이 됩니다. 이 패턴은 범위 기반 for문과 결합될 때 특히 헷갈립니다. for (auto x : getVector())는 범위 식 전체가 수명 연장되어 안전하지만, for (auto x : getConfig().items())처럼 임시의 멤버 함수가 돌려준 참조를 순회하면 C++23 이전에는 댕글링입니다. 자세한 규칙은 범위 기반 for 에러 글에서 다룹니다. std::string_view와 std::span도 같은 부류로, 임시 문자열이나 벡터에서 뷰를 만들어 변수에 담으면 즉시 댕글링이 됩니다.
임시 객체 감지
class Tracker {
public:
Tracker() {
std::cout << "기본 생성자" << std::endl;
}
Tracker(const Tracker&) {
std::cout << "복사 생성자" << std::endl;
}
Tracker(Tracker&&) noexcept {
std::cout << "이동 생성자" << std::endl;
}
~Tracker() {
std::cout << "소멸자" << std::endl;
}
};
Tracker getTracker() {
return Tracker();
}
int main() {
std::cout << "=== 시작 ===" << std::endl;
auto t = getTracker();
std::cout << "=== 끝 ===" << std::endl;
}
이렇게 특수 멤버 함수마다 출력을 넣은 추적 클래스는 “어디서 임시 객체와 복사가 생기는지”를 눈으로 확인하는 가장 확실한 방법입니다. C++17 이상에서 이 코드를 실행하면 “기본 생성자 → === 끝 === → 소멸자”만 출력되고 복사·이동 생성자는 한 번도 호출되지 않습니다. getTracker()가 prvalue를 반환하고 t를 초기화하므로 복사 생략이 보장되기 때문입니다. C++14 이하에서 -fno-elide-constructors(GCC/Clang)를 주고 컴파일하면 생략이 꺼져 이동 생성자 호출이 두 번까지 보이는데, 이를 비교해 보면 복사 생략이 무엇을 없애 주는지 체감할 수 있습니다. 자기 클래스에 이런 출력을 잠시 넣어 두고 vector::push_back이나 함수 인자 전달을 테스트해 보면, 예상하지 못한 복사를 빠르게 찾아낼 수 있습니다.
성능 고려사항
// ❌ 불필요한 임시 객체
std::string s = "Hello";
s = s + " World"; // 임시 객체 생성
// ✅ 직접 수정
std::string s = "Hello";
s += " World"; // 임시 객체 없음
// ❌ 반복문에서 임시 객체
for (int i = 0; i < 1000; i++) {
std::string s = std::string("Hello"); // 매번 생성
}
// ✅ 재사용
std::string s;
for (int i = 0; i < 1000; i++) {
s = "Hello";
}
임시 객체 비용 분석:
#include <chrono>
#include <iostream>
#include <string>
class LargeObject {
std::string data_;
public:
LargeObject(const std::string& data) : data_(data) {}
LargeObject(const LargeObject& other) : data_(other.data_) {
// 복사 비용
}
LargeObject(LargeObject&& other) noexcept : data_(std::move(other.data_)) {
// 이동 비용 (작음)
}
};
// ❌ 임시 객체 많이 생성
LargeObject process1(const LargeObject& obj) {
LargeObject result = obj; // 복사
// 처리
return result; // 이동 또는 복사 생략
}
// ✅ 이동 의미론 활용
LargeObject process2(LargeObject obj) { // 이동으로 받기
// 처리
return obj; // 이동 또는 복사 생략
}
// ✅ 직접 수정
void process3(LargeObject& obj) { // 레퍼런스
// obj 직접 수정
}
최적화 팁:
| 상황 | 비권장 | 권장 |
|---|---|---|
| 문자열 연결 | s = s + " World"; | s += " World"; |
| 컨테이너 추가 | v = v + element; | v.push_back(element); |
| 반복 생성 | for(...) { T obj; } | T obj; for(...) { obj = ...; } |
| 함수 반환 | return std::move(local); | return local; |
실무 패턴
패턴 1: 체이닝
class StringBuilder {
std::string buffer_;
public:
StringBuilder& append(const std::string& str) {
buffer_ += str;
return *this; // 임시 객체 없음
}
std::string build() const {
return buffer_; // 복사 생략
}
};
// 사용
auto result = StringBuilder()
.append("Hello")
.append(" ")
.append("World")
.build();
이 체이닝이 안전한 이유는 StringBuilder() 임시가 전체 문장이 끝날 때까지 살아 있고, 마지막 build()가 문자열을 값으로 복사해 내보내기 때문입니다. 같은 빌더를 auto& sb = StringBuilder().append("Hello");처럼 참조로 받아 두면, append가 돌려준 *this 참조는 수명 연장을 일으키지 않으므로(함수가 반환한 참조) 다음 줄에서 sb는 댕글링입니다. 체이닝 API는 한 문장 안에서 끝내는 것이 원칙입니다.
build() const가 buffer_를 복사하는 것도 개선할 수 있습니다. 임시 빌더에서 호출될 때는 버퍼를 복사할 필요 없이 가져가면 되므로, std::string build() && { return std::move(buffer_); }처럼 우측값 참조 한정자(&&)를 붙인 오버로드를 추가하면 임시에서 호출할 때는 이동, 이름 있는 빌더에서 호출할 때는 복사를 하도록 나눌 수 있습니다.
패턴 2: 팩토리 함수
class Connection {
std::string host_;
int port_;
public:
Connection(std::string host, int port)
: host_(std::move(host)), port_(port) {}
};
// 임시 객체 반환 (복사 생략)
Connection createConnection(const std::string& type) {
if (type == "local") {
return Connection("localhost", 8080);
}
return Connection("remote.example.com", 443);
}
패턴 3: 이동 의미론
class Resource {
std::vector<int> data_;
public:
Resource(size_t size) : data_(size) {}
// 이동 생성자
Resource(Resource&& other) noexcept
: data_(std::move(other.data_)) {}
};
// 임시 객체를 이동으로 받기
void process(Resource&& res) {
Resource local = std::move(res); // 이동
}
int main() {
process(Resource(1000)); // 임시 객체 이동
}
FAQ
Q1: 임시 객체는 언제 생성되나요?
A:
- 함수 반환 시
- 타입 변환 시
- 연산자 사용 시
- 명시적 생성 시 (예:
Widget(10))
Q2: 임시 객체의 수명은?
A:
- 기본: 전체 표현식이 끝나면 소멸
- 연장: const 레퍼런스 또는 우측값 레퍼런스로 바인딩 시 수명 연장
Q3: 성능 영향은?
A:
- RVO/NRVO로 대부분 최적화됨
- 이동 의미론으로 복사 비용 감소
- 불필요한 임시 객체는 성능 저하
Q4: 댕글링 레퍼런스를 방지하려면?
A:
- 값으로 저장 (C++17부터 추가 복사 없음)
- const 레퍼런스로 수명 연장 (임시에 직접 바인딩할 때만 동작)
- 임시 객체의 멤버 함수·게터가 반환한 참조, 포인터, 반복자,
string_view는 변수에 저장하지 않기
Q5: 임시 객체를 최적화하는 방법은?
A:
+=등 직접 수정 연산자 사용- 불필요한 타입 변환 피하기
- 이동 의미론 활용
- RVO/NRVO 활용 (반환 시
std::move사용 안 함)
Q6: 임시 객체를 비const 레퍼런스로 받을 수 있나요?
A: 불가능합니다. 임시 객체는 const 레퍼런스 또는 우측값 레퍼런스로만 바인딩할 수 있습니다.
void func1(std::string& s) {} // 비const 레퍼런스
void func2(const std::string& s) {} // const 레퍼런스
void func3(std::string&& s) {} // 우측값 레퍼런스
// func1("Hello"); // 에러
func2("Hello"); // OK
func3("Hello"); // OK
Q7: 임시 객체 학습 리소스는?
A:
- “More Effective C++” by Scott Meyers (Item 19: 임시 객체의 원천을 이해하라)
- “C++ Primer” by Lippman, Lajoie, Moo
- cppreference.com - Lifetime
관련 글: Copy Elision, RVO/NRVO, Move Semantics.
임시 객체는 표현식 평가 중 생성되는 이름 없는 객체로, 표현식 끝에 소멸되지만 const 레퍼런스로 수명을 연장할 수 있습니다.
같이 보면 좋은 글
- C++ 댕글링 레퍼런스: 지역 변수 반환·임시 객체·컨테이너 요소에서 생기는 원인과 탐지
- C++ 객체 수명과 저장 기간: 생성·소멸 순서, 임시 객체 수명, 댕글링 참조
- C++ Copy Elision 심화 | RVO·NRVO·필수 생략·예외 안전
- C++ 시리즈 전체 보기