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
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. C++ return: basics, return types, references, pointers, RVO/NRVO, multiple return values (pair, tuple, optional), and co…
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- [2026] C++ Copy Elision 심화 | RVO·NRVO·필수 생략·예외 안전
- C++ RVO/NRVO | ‘Return Value Optimization’ 가이드
- C++ Copy Initialization | ‘복사 초기화’ 가이드
Keywords Covered in This Article (Related Search Terms)
This article covers C++, return, function, RVO, return statement.