C++ return Statement — Complete Guide
이 글의 핵심
A practical guide to the C++ return statement: value and void returns, references, pointers, RVO, and safe patterns.
Introduction
The return statement ends a function and passes a value back to the caller. In C++, RVO (Return Value Optimization) lets the compiler eliminate unnecessary copies on return, which helps you write efficient code.
What you actually see at work
When you learn, everything feels neat and theoretical. Production is different: legacy code, tight deadlines, and bugs you did not expect. The topics here started as theory; they clicked once you apply them and think, “So that is why it is designed this way.”
What stuck from an early project was trial and error. I followed the book and still got stuck for days. A senior’s review surfaced the real issue, and I learned a lot from that. This article covers not only theory but also traps and fixes you are likely to hit in practice.
1. return basics
Returning a value
#include <iostream>
int add(int a, int b) {
return a + b; // return a value
}
double divide(int a, int b) {
return static_cast<double>(a) / b;
}
int main() {
int sum = add(10, 20);
double result = divide(10, 3);
std::cout << "sum: " << sum << std::endl; // 30
std::cout << "division: " << result << std::endl; // 3.33333
return 0;
}
void return
#include <iostream>
void printMessage(const std::string& msg) {
std::cout << msg << std::endl;
return; // optional at end of void function
}
void processData(int value) {
if (value < 0) {
std::cout << "Cannot process negative values" << std::endl;
return; // early exit
}
std::cout << "processing: " << value << std::endl;
}
int main() {
printMessage("Hello");
processData(-5);
processData(10);
return 0;
}
2. Return types
Returning by value
#include <string>
#include <vector>
// fundamental type
int getValue() {
return 42;
}
// object return (RVO)
std::string getName() {
return "Alice";
}
// container return (RVO)
std::vector<int> getNumbers() {
return {1, 2, 3, 4, 5};
}
Returning references
#include <iostream>
class Array {
int data[10] = {0};
public:
// OK: reference to a member
int& operator[](size_t index) {
return data[index];
}
// OK: const reference
const int& at(size_t index) const {
return data[index];
}
};
// OK: reference to a static
int& getGlobalCounter() {
static int counter = 0;
return counter;
}
int main() {
Array arr;
arr[0] = 42; // modify via reference
int& counter = getGlobalCounter();
counter++;
std::cout << arr[0] << ", " << counter << std::endl;
return 0;
}
Returning pointers
#include <iostream>
// OK: return dynamically allocated memory
int* createInt(int value) {
return new int(value); // caller must delete
}
// OK: pointer to member storage
class Container {
int data[10];
public:
int* getData() {
return data;
}
};
// BAD: pointer to a local
int* getBad() {
int x = 10;
return &x; // dangling pointer!
}
int main() {
int* ptr = createInt(42);
std::cout << *ptr << std::endl;
delete ptr; // manual cleanup
return 0;
}
3. RVO (Return Value Optimization)
RVO basics
Example main and helper usage:
#include <iostream>
#include <string>
class Widget {
public:
Widget() {
std::cout << "Widget constructed" << std::endl;
}
Widget(const Widget&) {
std::cout << "Widget copy" << std::endl;
}
Widget(Widget&&) noexcept {
std::cout << "Widget move" << std::endl;
}
};
// RVO: copy/move may be elided
Widget createWidget() {
return Widget(); // construct in place
}
int main() {
Widget w = createWidget();
// prints: "Widget constructed" (no copy/move)
return 0;
}
NRVO (Named RVO)
#include <string>
#include <iostream>
std::string createString() {
std::string s = "Hello"; // local
return s; // NRVO may elide the copy
}
int main() {
std::string str = createString();
std::cout << str << std::endl;
return 0;
}
Do not block RVO
#include <string>
// BAD: std::move can block NRVO
std::string bad() {
std::string s = "Hello";
return std::move(s); // interferes with NRVO
}
// GOOD: plain return (RVO applies)
std::string good() {
std::string s = "Hello";
return s; // NRVO applies
}
4. Multiple return values
std::pair
Example main usage:
#include <utility>
#include <iostream>
std::pair<bool, int> divide(int a, int b) {
if (b == 0) {
return {false, 0};
}
return {true, a / b};
}
int main() {
auto [success, result] = divide(10, 2);
if (success) {
std::cout << "result: " << result << std::endl;
} else {
std::cout << "cannot divide by zero" << std::endl;
}
return 0;
}
std::tuple
#include <tuple>
#include <iostream>
#include <string>
std::tuple<int, std::string, double> getUserInfo() {
return {25, "Hong Gildong", 175.5};
}
int main() {
auto [age, name, height] = getUserInfo();
std::cout << "name: " << name << std::endl;
std::cout << "age: " << age << std::endl;
std::cout << "height: " << height << std::endl;
return 0;
}
std::optional (C++17)
#include <optional>
#include <iostream>
#include <string>
std::optional<int> parseInt(const std::string& str) {
try {
return std::stoi(str);
} catch (...) {
return std::nullopt;
}
}
int main() {
auto result = parseInt("123");
if (result.has_value()) {
std::cout << "value: " << result.value() << std::endl;
} else {
std::cout << "parse failed" << std::endl;
}
// or
int value = parseInt("123").value_or(0);
return 0;
}
5. Common problems
Problem 1: returning a reference to a local
#include <iostream>
// BAD: dangling reference
const std::string& bad() {
std::string s = "Hello";
return s; // s is destroyed when the function returns
}
// GOOD: return by value (RVO)
std::string good() {
std::string s = "Hello";
return s; // safe
}
int main() {
// const std::string& ref = bad(); // undefined behavior
std::string str = good(); // safe
std::cout << str << std::endl;
return 0;
}
Problem 2: not returning on all paths
#include <iostream>
// BAD: missing return
int bad(bool flag) {
if (flag) {
return 10;
}
// missing return (typically a warning)
}
// GOOD: return on every path
int good(bool flag) {
if (flag) {
return 10;
}
return 0; // else branch
}
int main() {
std::cout << good(true) << std::endl;
std::cout << good(false) << std::endl;
return 0;
}
Problem 3: return type mismatch
Example for bad and related functions:
// BAD: type mismatch
int bad() {
return "Hello"; // const char* -> int (warning/error)
}
// GOOD: correct type
std::string good() {
return "Hello";
}
// GOOD: auto deduction
auto autoGood() {
return "Hello"; // deduced as const char*
}
Problem 4: unnecessary std::move
#include <string>
// BAD: std::move blocks RVO
std::string bad() {
std::string s = "Hello";
return std::move(s); // unnecessary
}
// GOOD: plain return
std::string good() {
std::string s = "Hello";
return s; // RVO applies
}
6. Practical example: error handling
#include <optional>
#include <string>
#include <iostream>
#include <fstream>
class FileReader {
public:
// explicit success/failure
std::optional<std::string> readFile(const std::string& path) {
std::ifstream file(path);
if (!file.is_open()) {
return std::nullopt; // failure
}
std::string content;
std::string line;
while (std::getline(file, line)) {
content += line + "\n";
}
return content; // success
}
// errors via exception
std::string readFileOrThrow(const std::string& path) {
std::ifstream file(path);
if (!file.is_open()) {
throw std::runtime_error("failed to open file: " + path);
}
std::string content;
std::string line;
while (std::getline(file, line)) {
content += line + "\n";
}
return content;
}
};
int main() {
FileReader reader;
// optional
auto content = reader.readFile("data.txt");
if (content.has_value()) {
std::cout << "file contents:\n" << content.value() << std::endl;
} else {
std::cout << "failed to read file" << std::endl;
}
// exception
try {
std::string content2 = reader.readFileOrThrow("data.txt");
std::cout << content2 << std::endl;
} catch (const std::exception& e) {
std::cerr << "error: " << e.what() << std::endl;
}
return 0;
}
Summary
Key takeaways
- return: ends the function and returns a value.
- By value: copy or move (often optimized with RVO).
- By reference: safe for members or static storage only.
- Pointers: watch object lifetime and ownership.
- RVO: elision of return-value copies (stronger guarantees since C++17).
- Multiple values:
std::pair,std::tuple,std::optional.
Choosing a return type
| Situation | Preferred return | Why |
|---|---|---|
| Fundamental types | By value | cheap to copy |
| Objects | By value | RVO applies |
| Mutate a member | Reference | direct modification |
| Possible failure | std::optional | clear failure channel |
| Several values | std::tuple | structured return |
| Rich error info | Exception | propagate error details |
Practical tips
Safety:
- Return locals by value, not by reference.
- Return on every path (enable
-Wreturn-typeor equivalent). - Document lifetime when returning raw pointers.
Performance:
- Trust RVO; return by value.
- Avoid
std::moveon return locals (can block NRVO). - Large objects are still usually fine by value with RVO.
Readability:
- Use
std::optionalfor clear failure. - Use
std::tuplefor multiple values. - Use C++17 structured bindings.
Next steps
Related posts
- C++ std::function vs function pointer
- C++ default arguments
- C++ RVO·NRVO
- C++ functions
- C++ function overloading
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. C++ return: basics, return types, references, pointers, RVO/NRVO, multiple return values (pair, tuple, optional), and co… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- [2026] C++ Copy Elision 심화 | RVO·NRVO·필수 생략·예외 안전
- C++ RVO/NRVO | ‘Return Value Optimization’ 가이드
- C++ Copy Initialization | ‘복사 초기화’ 가이드
이 글에서 다루는 키워드 (관련 검색어)
C++, return, function, RVO, return statement 등으로 검색하시면 이 글이 도움이 됩니다.