[2026] C++ std::optional Complete Guide | nullopt, Monadic Ops (C++23), and Patterns
이 글의 핵심
std::optional vs nullptr and exceptions: value_or, and_then, transform, or_else, performance, and production error-handling patterns (C++17–C++23).
Introduction: “It may not have value, so how do I express it?”
Problems encountered in practice
We often encounter situations like this during C++ development:
- Search Failed — Searched by user ID, but nothing was found. return
nullptr? exception? Special value -1? - Missing configuration value — Specific key is missing from configuration file. How to handle defaults?
- Parse Failed — Failed to convert string to number. Exceptions are excessive and error codes are inconvenient.
- Cache Miss — No data in cache. Checking
nullptrevery time is inconvenient - Partial initialization — Some fields of the object are optional. Pointers are a memory management burden.
Problems with existing methods:
| method | Problem |
|------|--------|
|
nullptr| Memory management burden, risk of crash when dereferenced | | special value (-1, INT_MIN) | Difficult to distinguish from valid values, not type safe | | exception | Expected failures are overkill, performance overhead | |std::pair<bool, T>| Long-winded, prone to mistakes | Resolved with std::optional: 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Conventional method
int* findUser(int id) {
// ...
return nullptr; // Memory management required
}
// ✅ Optional use
std::optional<int> findUser(int id) {
// ...
return std::nullopt; // safe and clear
}
target:
- std::optional basics (creation, access, check)
- C++23 monadic operations (and_then, or_else, transform)
- Practical patterns (error handling, API design, chaining)
- Performance considerations (when not to use)
- Comparison with other types (variant, expected, exception)
- Frequent mistakes and solutions
- Production Pattern Required Environment: C++17 or higher (C++23 features listed separately)
index
- Problem scenario: Handling situations where there is no value
- std::optional basics
- C++23 monadic operations
- Practical error handling pattern
- Performance considerations
- Comparison with other types
- Complete example collection
- Frequently occurring mistakes and solutions
- Best practices/best practices
- Production Patterns
- Organization and checklist
1. Problem Scenario: Handling a Missing Value Situation
Scenario 1: Database lookup failure
다음은 cpp를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Use of pointers (memory management burden)
User* findUserById(int id) {
// DB query
if (/* found */) {
return new User{id, "Alice"}; // 💥 Who deletes?
}
return nullptr;
}
// use
User* user = findUserById(123);
if (user != nullptr) {
std::cout << user->name << std::endl;
delete user; // 💥 Memory leak if you forget
}
Caution: The pattern of passing DB search results to new is prone to leaks when exceptions or early returns occur. If ownership is required, consider unique_ptr first, and if it “may not exist”, consider optional first.
다음은 cpp를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ Optional use (safe and clear)
std::optional<User> findUserById(int id) {
// DB query (actually execute DB query)
if (/* if the user was found */) {
// Returns a User object wrapped in optional
return User{id, "Alice"};
}
// If not found, return nullopt (specifying that there is no value)
return std::nullopt;
}
// Example usage
if (auto user = findUserById(123)) {
// If user has a value (found), execute this block
std::cout << user->name << std::endl; // ✅ Automatic cleanup, no memory management required
}
// When the user leaves the if block, the user is automatically destroyed.
Scenario 2: Parsing configuration file
아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Use of special values (difficult to distinguish from valid values)
int getTimeout(const Config& config) {
if (config.has("timeout")) {
return config.getInt("timeout");
}
return -1; // 💥 -1 may be a valid value
}
// use
int timeout = getTimeout(config);
if (timeout == -1) { // 💥 It is unclear whether -1 is the actual value or an error.
timeout = 30; // default
}
아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ Use optional (clear meaning)
std::optional<int> getTimeout(const Config& config) {
if (config.has("timeout")) {
return config.getInt("timeout");
}
return std::nullopt;
}
// use
int timeout = getTimeout(config).value_or(30); // ✅ Concise and clear
Scenario 3: String parsing
아래 코드는 cpp를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ Use exceptions (overkill for expected failures)
int parseInt(const std::string& str) {
try {
return std::stoi(str);
} catch (...) {
throw std::runtime_error("Parse failed"); // 💥 Exceptions are costly
}
}
다음은 cpp를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ Use optional (expected failure)
std::optional<int> parseInt(const std::string& str) {
try {
return std::stoi(str);
} catch (...) {
return std::nullopt; // ✅ Expected failures are optional.
}
}
// use
if (auto num = parseInt("123")) {
std::cout << "Parsed: " << *num << std::endl;
} else {
std::cout << "Parse failed" << std::endl;
}
Note: Empty strings, overflows, etc. are still subject to stoi’s exception policy. For super-fast routes, consider changing to from_chars, etc.
Scenario 4: Cache Lookup
아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Use pair<bool, T> (wordy)
std::pair<bool, std::string> getCached(const std::string& key) {
if (cache.contains(key)) {
return {true, cache[key]};
}
return {false, ""}; // 💥 An empty string can also be a valid value
}
// use
auto [found, value] = getCached("user:123");
if (found) { // 💥 Easy to make mistakes
std::cout << value << std::endl;
}
아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ Optional use (simple and safe)
// 실행 예제
std::optional<std::string> getCached(const std::string& key) {
if (cache.contains(key)) {
return cache[key];
}
return std::nullopt;
}
// use
if (auto value = getCached("user:123")) {
std::cout << *value << std::endl; // ✅ Clear
}
다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
flowchart TB
subgraph Problems[Situation with no value]
P1[Search Failed]
P2[Setting Missing]
P3[parse failed]
P4[cache miss]
end
subgraph OldSolutions["Old Solutions (Problems)"]
O1[nullptr - memory management]
O2[Special value - ambiguous]
O3[Exception - Excessive]
O4[pair - wordy]
end
subgraph NewSolution[std optional]
N1[Safety]
N2[Clear]
N3[Concise]
end
P1 --> O1
P2 --> O2
P3 --> O3
P4 --> O4
O1 --> N1
O2 --> N2
O3 --> N3
O4 --> N1
2. std::optional basis
Creation and initialization
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <optional>
#include <iostream>
#include <string>
// 1. Create default (no value)
std::optional<int> opt1;
std::optional<int> opt2 = std::nullopt;
// 2. Initialize with value
std::optional<int> opt3 = 42;
std::optional<int> opt4{42};
// 3. make_optional
auto opt5 = std::make_optional<int>(42);
auto opt6 = std::make_optional<std::string>("Hello");
// 4. Create in_place (complex type)
struct Point {
int x, y;
Point(int x, int y) : x(x), y(y) {}
};
std::optional<Point> opt7{std::in_place, 10, 20}; // Create Point(10, 20) directly
// 5. emplace (assign value later)
std::optional<Point> opt8;
opt8.emplace(30, 40); // Create Point(30, 40)
Checking and accessing values
아래 코드는 mermaid를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
flowchart TD
Start[Check optional value] --> HasValue{has_value?}
HasValue -->|true| Access[value access]
HasValue -->|false| Handle[handle nullopt]
Access --> Method1[value - exception possible]
Access --> Method2[operator* - UB available]
Access --> Method3[value_or - safe]
Method3 --> Safe [return default]
style Method3 fill:#90EE90
style Safe fill:#90EE90
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <optional>
#include <iostream>
int main() {
std::optional<int> opt = 42;
// 1. has_value() - explicit check
if (opt.has_value()) {
std::cout << "Has value: " << opt.value() << std::endl;
}
// 2. operator bool - implicit conversion
if (opt) {
std::cout << "Has value: " << *opt << std::endl;
}
// 3. value() - exceptions may occur
try {
std::optional<int> empty;
int x = empty.value(); // 💥 std::bad_optional_access exception
} catch (const std::bad_optional_access& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
// 4. operator* - dereference (UB if no value)
std::optional<int> opt2 = 100;
std::cout << *opt2 << std::endl; // 100
// 5. operator-> - member access
std::optional<std::string> opt3 = "Hello";
std::cout << opt3->length() << std::endl; // 5
// 6. value_or() - Provides default value (safest)
std::optional<int> empty;
int x = empty.value_or(0); // 0 (default)
std::cout << x << std::endl;
return 0;
}
Note: When there is no value, operator* has undefined behavior and value() throws bad_optional_access. In defensive code, leave value_or or the preceding if as the default.
Editing and removing values
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <optional>
#include <iostream>
int main() {
std::optional<int> opt = 42;
// 1. Change value by substitution
opt = 100;
std::cout << *opt << std::endl; // 100
// 2. reset() - remove value
opt.reset();
std::cout << opt.has_value() << std::endl; // false
// 3. nullopt assignment
opt = 42;
opt = std::nullopt;
std::cout << opt.has_value() << std::endl; // false
// 4. emplace() – Create a new value
opt.emplace(200);
std::cout << *opt << std::endl; // 200
// 5. swap()
std::optional<int> opt1 = 10;
std::optional<int> opt2 = 20;
opt1.swap(opt2);
std::cout << *opt1 << " " << *opt2 << std::endl; // 20 10
return 0;
}
Comparison operations
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <optional>
#include <iostream>
int main() {
std::optional<int> a = 10;
std::optional<int> b = 20;
std::optional<int> empty;
// Compare optionals
std::cout << (a == b) << std::endl; // false
std::cout << (a < b) << std::endl; // true
std::cout << (a == empty) << std::endl; // false
// Direct comparison with value
std::cout << (a == 10) << std::endl; // true
std::cout << (a != 20) << std::endl; // true
// Compare with nullopt
std::cout << (empty == std::nullopt) << std::endl; // true
std::cout << (a != std::nullopt) << std::endl; // true
// Comparison Rules
// - nullopt < any value
// - If there are values, compare them.
std::optional<int> opt1 = 5;
std::optional<int> opt2;
std::cout << (opt2 < opt1) << std::endl; // true (nullopt < 5)
return 0;
}
3. C++23 monadic operations
and_then: Chaining (flatMap)
Available starting from C++23 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <optional>
#include <iostream>
#include <string>
// User Lookup
std::optional<int> findUserId(const std::string& username) {
if (username == "alice") return 1;
if (username == "bob") return 2;
return std::nullopt;
}
// User email inquiry
std::optional<std::string> findEmail(int userId) {
if (userId == 1) return "alice@example.com";
if (userId == 2) return "bob@example.com";
return std::nullopt;
}
int main() {
// ❌ C++17 method (nested if)
auto userId = findUserId("alice");
if (userId) {
auto email = findEmail(*userId);
if (email) {
std::cout << "Email: " << *email << std::endl;
}
}
// ✅ C++23 and_then (Chaining)
auto email = findUserId("alice")
.and_then(findEmail);
if (email) {
std::cout << "Email: " << *email << std::endl;
}
// Handle failure cases naturally
auto noEmail = findUserId("charlie") // return nullopt
.and_then(findEmail); // not running
std::cout << noEmail.has_value() << std::endl; // false
return 0;
}
transform: Transform value (map)
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <optional>
#include <iostream>
#include <string>
int main() {
std::optional<int> opt = 42;
// ❌ C++17 method
std::optional<std::string> str1;
if (opt) {
str1 = std::to_string(*opt);
}
// ✅ C++23 transform
auto str2 = opt.transform( {
return std::to_string(x);
});
std::cout << *str2 << std::endl; // "42"
// chaining
auto result = std::optional<int>{10}
.transform( { return x * 2; }) // 20
.transform( { return x + 5; }) // 25
.transform( { return std::to_string(x); }); // "25"
std::cout << *result << std::endl; // "25"
// Empty optional is not converted
std::optional<int> empty;
auto result2 = empty.transform( {
std::cout << "Not executed" << std::endl;
return x * 2;
});
std::cout << result2.has_value() << std::endl; // false
return 0;
}
or_else: Provides alternative value
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <optional>
#include <iostream>
std::optional<int> getFromCache(const std::string& key) {
// cache lookup
return std::nullopt; // Miss Cathy
}
std::optional<int> getFromDatabase(const std::string& key) {
// DB query
return 42;
}
int main() {
// ❌ C++17 method
auto value1 = getFromCache("user:123");
if (!value1) {
value1 = getFromDatabase("user:123");
}
// ✅ C++23 or_else
auto value2 = getFromCache("user:123")
.or_else( { return getFromDatabase("user:123"); });
std::cout << *value2 << std::endl; // 42
// Multiple alternative source chaining
auto value3 = getFromCache("user:123")
.or_else( { return getFromDatabase("user:123"); })
.or_else( { return std::optional<int>{0}; }); // final default
return 0;
}
Practical Combinations: Complex Pipelines
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <optional>
#include <iostream>
#include <string>
#include <map>
struct User {
int id;
std::string name;
std::optional<std::string> email;
};
std::map<int, User> users = {
{1, {1, "Alice", "alice@example.com"}},
{2, {2, "Bob", std::nullopt}},
};
std::optional<User> findUser(int id) {
auto it = users.find(id);
if (it != users.end()) {
return it->second;
}
return std::nullopt;
}
int main() {
// User inquiry → Email extraction → Domain extraction
auto domain = findUser(1)
.and_then( { return u.email; })
.transform( {
size_t pos = email.find('@');
return email.substr(pos + 1);
});
if (domain) {
std::cout << "Domain: " << *domain << std::endl; // "example.com"
}
// User without email
auto noDomain = findUser(2)
.and_then( { return u.email; }) // return nullopt
.transform( {
// Not running
return email.substr(email.find('@') + 1);
});
std::cout << noDomain.has_value() << std::endl; // false
return 0;
}
4. Practical error handling patterns
Pattern 1: Parsing configuration file
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <optional>
#include <iostream>
#include <map>
#include <string>
class Config {
private:
std::map<std::string, std::string> data_;
public:
void set(const std::string& key, const std::string& value) {
data_[key] = value;
}
std::optional<std::string> getString(const std::string& key) const {
auto it = data_.find(key);
if (it != data_.end()) {
return it->second;
}
return std::nullopt;
}
std::optional<int> getInt(const std::string& key) const {
return getString(key).and_then( -> std::optional<int> {
try {
return std::stoi(s);
} catch (...) {
return std::nullopt;
}
});
}
std::optional<bool> getBool(const std::string& key) const {
return getString(key).transform( {
return s == "true" || s == "1";
});
}
};
int main() {
Config config;
config.set("port", "8080");
config.set("host", "localhost");
config.set("debug", "true");
config.set("invalid", "abc");
// If value exists, use; if not, default value.
int port = config.getInt("port").value_or(3000);
std::string host = config.getString("host").value_or("0.0.0.0");
bool debug = config.getBool("debug").value_or(false);
std::cout << "Port: " << port << std::endl;
std::cout << "Host: " << host << std::endl;
std::cout << "Debug: " << debug << std::endl;
// Handling invalid values
auto invalid = config.getInt("invalid");
if (!invalid) {
std::cerr << "Invalid integer value" << std::endl;
}
return 0;
}
Pattern 2: Cache System
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <optional>
#include <map>
#include <string>
#include <chrono>
#include <iostream>
template<typename K, typename V>
class TimedCache {
private:
struct Entry {
V value;
std::chrono::steady_clock::time_point expires_at;
};
std::map<K, Entry> data_;
std::chrono::seconds default_ttl_;
public:
explicit TimedCache(std::chrono::seconds ttl = std::chrono::seconds{60})
: default_ttl_(ttl) {}
void put(const K& key, const V& value) {
auto expires = std::chrono::steady_clock::now() + default_ttl_;
data_[key] = {value, expires};
}
std::optional<V> get(const K& key) {
auto it = data_.find(key);
if (it == data_.end()) {
return std::nullopt; // Miss Cathy
}
// Check expiration
if (std::chrono::steady_clock::now() > it->second.expires_at) {
data_.erase(it);
return std::nullopt; // expired
}
return it->second.value;
}
// Using C++23 or_else
template<typename F>
V getOrCompute(const K& key, F&& compute) {
return get(key).or_else([&]() -> std::optional<V> {
V value = compute();
put(key, value);
return value;
}).value();
}
};
int main() {
TimedCache<std::string, int> cache{std::chrono::seconds{5}};
// save cache
cache.put("user:123", 42);
// cache lookup
if (auto value = cache.get("user:123")) {
std::cout << "Cached: " << *value << std::endl;
}
// If not, calculate and save
int score = cache.getOrCompute("user:456", {
std::cout << "Computing..." << std::endl;
return 100; // Assuming you searched in DB
});
std::cout << "Score: " << score << std::endl;
return 0;
}
Pattern 3: API response processing
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <optional>
#include <string>
#include <iostream>
struct ApiResponse {
int status_code;
std::optional<std::string> body;
std::optional<std::string> error;
};
ApiResponse fetchData(const std::string& url) {
// HTTP request simulation
if (url == "https://api.example.com/users") {
return {200, R"({"users": []})", std::nullopt};
} else {
return {404, std::nullopt, "Not Found"};
}
}
int main() {
auto response = fetchData("https://api.example.com/users");
if (response.status_code == 200 && response.body) {
std::cout << "Success: " << *response.body << std::endl;
} else if (response.error) {
std::cerr << "Error: " << *response.error << std::endl;
}
// Utilizing C++23 transform
auto bodyLength = response.body.transform( {
return s.length();
});
std::cout << "Body length: " << bodyLength.value_or(0) << std::endl;
return 0;
}
5. Performance considerations
Memory overhead
#include <optional>
#include <iostream>
struct SmallType {
int x;
};
struct LargeType {
int data[1000];
};
int main() {
std::cout << "int: " << sizeof(int) << " bytes" << std::endl;
std::cout << "optional<int>: " << sizeof(std::optional<int>) << " bytes" << std::endl;
// Output: int: 4 bytes, optional<int>: 8 bytes (bool flag + padding)
std::cout << "SmallType: " << sizeof(SmallType) << " bytes" << std::endl;
std::cout << "optional<SmallType>: " << sizeof(std::optional<SmallType>) << " bytes" << std::endl;
std::cout << "LargeType: " << sizeof(LargeType) << " bytes" << std::endl;
std::cout << "optional<LargeType>: " << sizeof(std::optional<LargeType>) << " bytes" << std::endl;
// Even for large types, only the bool flag is added (approximately 1 byte + padding)
return 0;
}
Conclusion: The overhead of optional is mostly 1 byte + alignment padding.
When to avoid optional
다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Bad use: always has value
std::optional<int> getUserAge(int userId) {
// Optional is not necessary if the age is always returned.
return 25;
}
// ✅ Good use: When it may not have value.
std::optional<int> getUserAge(int userId) {
if (userId < 0) {
return std::nullopt; // invalid user
}
return 25;
}
// ❌ Bad use: Performance critical loops
for (int i = 0; i < 1000000; ++i) {
std::optional<int> result = compute(i); // Create optional every time
if (result) {
process(*result);
}
}
// ✅ Good use: handling special values
for (int i = 0; i < 1000000; ++i) {
int result = compute(i); // -1 = failure
if (result != -1) {
process(result);
}
}
// ❌ Bad use: making references optional
// std::optional<T&> is not possible
// std::optional<std::reference_wrapper<T>> is complicated
// ✅ Good use: Using pointers
T* ptr = findObject(); // nullptr possible
Performance Benchmarks
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <optional>
#include <chrono>
#include <iostream>
// 1. optional vs pointer
void benchmarkOptional() {
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 10000000; ++i) {
std::optional<int> opt = (i % 2 == 0) ? std::optional<int>{i} : std::nullopt;
if (opt) {
volatile int x = *opt; // Avoid optimization
}
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "optional: " << duration.count() << "ms" << std::endl;
}
void benchmarkPointer() {
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 10000000; ++i) {
int* ptr = (i % 2 == 0) ? new int(i) : nullptr;
if (ptr) {
volatile int x = *ptr;
delete ptr;
}
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "pointer: " << duration.count() << "ms" << std::endl;
}
int main() {
benchmarkOptional(); // About 50ms
benchmarkPointer(); // Approximately 500ms (new/delete overhead)
// Optional is much faster!
return 0;
}
6. Compare with other types
optional vs variant
#include <optional>
#include <variant>
#include <string>
#include <iostream>
// optional: with or without value
std::optional<int> divide(int a, int b) {
if (b == 0) return std::nullopt;
return a / b;
}
// variant: one of several types
std::variant<int, std::string> parseValue(const std::string& str) {
try {
return std::stoi(str);
} catch (...) {
return "Parse error: " + str;
}
}
int main() {
// Use optional
if (auto result = divide(10, 2)) {
std::cout << "Result: " << *result << std::endl;
}
// Use variant
auto value = parseValue("123");
if (std::holds_alternative<int>(value)) {
std::cout << "Integer: " << std::get<int>(value) << std::endl;
} else {
std::cout << "Error: " << std::get<std::string>(value) << std::endl;
}
return 0;
}
| Features | optional | variant<T, E> |
|---|---|---|
| Use | with or without value | One of several types |
| error information | none (nullopt only) | Error type can be saved |
| size | sizeof(T) + 1 | max(sizeof(T), sizeof(E)) + tag |
| use | simple failure | Detailed error information required |
optional vs exception
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <optional>
#include <stdexcept>
#include <iostream>
// use exceptions
int parseIntException(const std::string& str) {
try {
return std::stoi(str);
} catch (const std::exception& e) {
throw std::runtime_error("Parse failed: " + str);
}
}
// Use optional
std::optional<int> parseIntOptional(const std::string& str) {
try {
return std::stoi(str);
} catch (...) {
return std::nullopt;
}
}
int main() {
// Exception: Exceptional Circumstances
try {
int x = parseIntException("abc");
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
// optional: expected failure
if (auto x = parseIntOptional("abc")) {
std::cout << "Parsed: " << *x << std::endl;
} else {
std::cout << "Parse failed (expected)" << std::endl;
}
return 0;
}
| Features | optional | exception |
|---|---|---|
| When to use | Expected failure | exceptional circumstances |
| Performance | Fast (quarterly) | Slow (stack unwinding) |
| error information | None | detailed message |
| Force processing | No | Yes (requires catch) |
| Code flow | explicit | implicit |
optional vs expected (C++23 proposal)
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// expected<T, E>: optional + error information
// To be standardized in C++23
template<typename T, typename E>
class expected {
// Save T or E
// Similar to optional<T> but includes error information
};
// Example usage
expected<int, std::string> divide(int a, int b) {
if (b == 0) {
return unexpected{"Division by zero"};
}
return a / b;
}
// Compare to optional
std::optional<int> divideOptional(int a, int b) {
if (b == 0) return std::nullopt; // No error information
return a / b;
}
Related article: Learn practical patterns for using the two types together at Using Optional and Variant.
7. Complete set of examples
Example 1: JSON parser
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <optional>
#include <string>
#include <map>
#include <iostream>
class JsonValue {
public:
std::map<std::string, std::string> data;
std::optional<std::string> getString(const std::string& key) const {
auto it = data.find(key);
return (it != data.end()) ? std::optional{it->second} : std::nullopt;
}
std::optional<int> getInt(const std::string& key) const {
return getString(key).and_then( -> std::optional<int> {
try {
return std::stoi(s);
} catch (...) {
return std::nullopt;
}
});
}
std::optional<bool> getBool(const std::string& key) const {
return getString(key).transform( {
return s == "true";
});
}
};
int main() {
JsonValue json;
json.data[name] = "Alice";
json.data[age] = "30";
json.data[active] = "true";
// safe access
auto name = json.getString("name").value_or("Unknown");
auto age = json.getInt("age").value_or(0);
auto active = json.getBool("active").value_or(false);
std::cout << "Name: " << name << std::endl;
std::cout << "Age: " << age << std::endl;
std::cout << "Active: " << active << std::endl;
return 0;
}
8. Common mistakes and solutions
Mistake 1: Exception when calling value()
다음은 cpp를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Wrong way
std::optional<int> empty;
int x = empty.value(); // 💥 std::bad_optional_access exception
// ✅ Correct method 1: check has_value()
if (empty.has_value()) {
int x = empty.value();
}
// ✅ Correct method 2: operator bool
if (empty) {
int x = *empty;
}
// ✅ Correct method 3: value_or() (safest)
int x = empty.value_or(0);
Mistake 2: Using it like a pointer
다음은 cpp를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Wrong way
std::optional<int> opt = 42;
if (opt != nullptr) { // 💥 Compilation error
}
// ✅ The right way
if (opt.has_value()) {
// ...
}
// or
if (opt) {
// ...
}
// Compare with nullopt
if (opt != std::nullopt) {
// ...
}
Mistake 3: Trying optional<T&>
다음은 cpp를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Wrong method (compilation error)
int x = 10;
// std::optional<int&> opt = x; // 💥 Impossible
// ✅ Correct method 1: reference_wrapper
std::optional<std::reference_wrapper<int>> opt = std::ref(x);
if (opt) {
opt->get() = 20; // x changed to 20
}
// ✅ Correct way 2: Use pointers (simpler)
std::optional<int*> opt2 = &x;
if (opt2) {
**opt2 = 30;
}
Mistake 4: Unnecessary copying
다음은 cpp를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Inefficient
std::optional<std::string> getLargeString() {
std::string large(10000, 'x');
return large; // Copy occurs (RVO may not work)
}
// ✅ Efficient (utilizing RVO)
std::optional<std::string> getLargeString() {
if (/* condition */) {
return std::string(10000, 'x'); // RVO
}
return std::nullopt;
}
// ✅ Movement explicit
std::optional<std::string> getLargeString() {
std::string large(10000, 'x');
return std::move(large); // movement
}
Mistake 5: Nesting optional<optional>
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Complex and confusing
std::optional<std::optional<int>> nested() {
return std::optional<int>{42}; // 💥 nested optional
}
// ✅ Single optional use
std::optional<int> simple() {
return 42;
}
// If you really need overlap, consider variant
std::variant<int, std::string, std::monostate> alternative() {
return 42;
}
Mistake 6: Overuse in performance-critical code
다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Creating optional in hot loop
for (int i = 0; i < 1000000; ++i) {
std::optional<int> result = compute(i);
if (result) {
process(*result);
}
}
// ✅ Use special values (faster)
for (int i = 0; i < 1000000; ++i) {
int result = compute(i); // -1 = failure
if (result != -1) {
process(result);
}
}
9. Best practices/best practices
1. Use value_or() as default
아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ Concise and safe
int port = config.getInt("port").value_or(8080);
// ❌ Wordy
int port;
if (auto p = config.getInt("port")) {
port = *p;
} else {
port = 8080;
}
2. Used as function return type
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ✅ Express the possibility of failure as a type
std::optional<User> findUser(int id);
// ❌ Pointers (memory management burden)
User* findUser(int id);
// ❌ Exceptions (overkill for expected failures)
User findUser(int id); // Exception if not
3. Utilizing C++23 monadic operations
아래 코드는 cpp를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ Concise with chaining
auto email = findUser(123)
.and_then( { return u.getEmail(); })
.value_or("no-reply@example.com");
// ❌ Nested if
std::string email = "no-reply@example.com";
if (auto user = findUser(123)) {
if (auto e = user->getEmail()) {
email = *e;
}
}
4. Utilizing structured binding (C++17)
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
struct Result {
std::optional<int> value;
std::optional<std::string> error;
};
Result compute() {
// ...
return {42, std::nullopt};
}
// ✅ Structured bindings
auto [value, error] = compute();
if (value) {
std::cout << "Success: " << *value << std::endl;
} else if (error) {
std::cerr << "Error: " << *error << std::endl;
}
5. Clear naming
아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ✅ Clear function names
std::optional<User> tryFindUser(int id);
std::optional<int> maybeParseInt(const std::string& str);
// ❌ Ambiguous name
User getUser(int id); // What happens if you don't have it?
int parseInt(const std::string& str); // What if I fail?
10. production pattern
Pattern 1: Optional Chaining Helper
아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T, typename F>
auto map_optional(const std::optional<T>& opt, F&& func)
-> std::optional<std::invoke_result_t<F, T>> {
if (opt) {
return func(*opt);
}
return std::nullopt;
}
// use
std::optional<int> opt = 42;
auto result = map_optional(opt, { return x * 2; });
Pattern 2: Combining multiple optionals
다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T>
std::optional<std::vector<T>> collect_optionals(
const std::vector<std::optional<T>>& opts) {
std::vector<T> result;
for (const auto& opt : opts) {
if (!opt) {
return std::nullopt; // Failure if one is missing
}
result.push_back(*opt);
}
return result;
}
// use
std::vector<std::optional<int>> opts = {1, 2, 3};
if (auto values = collect_optionals(opts)) {
// All values are present
}
Pattern 3: Lazy evaluation
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename F>
class LazyOptional {
private:
F compute_;
mutable std::optional<std::invoke_result_t<F>> cache_;
public:
explicit LazyOptional(F func) : compute_(std::move(func)) {}
auto get() const -> std::optional<std::invoke_result_t<F>> {
if (!cache_) {
cache_ = compute_();
}
return cache_;
}
};
// use
LazyOptional expensive{ -> std::optional<int> {
// expensive calculation
return 42;
}};
// Compute only when needed
if (auto value = expensive.get()) {
std::cout << *value << std::endl;
}
11. Organize and Checklist
optional usage guide
| Situation | Use or not | alternative |
|---|---|---|
| Search may fail | ✅ Use | - |
| Missing setting value | ✅ Use | - |
| Parse failed | ✅ Use | - |
| always has value | ❌ Not necessary | General type |
| Detailed error information required | ❌ Inappropriate | variant, expected |
| Performance Critical | ❌ Carefully | special values, pointers |
| Save reference | ❌ Impossible | pointer, reference_wrapper |
Checklist
다음은 bash를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# ✅ When using optional
- [ ] Is this a situation where there may be no value?
- [ ] Is a default value provided with value_or()?
- [ ] Did you check has_value() before calling value()?
- [ ] Did you avoid unnecessary copying?
- [ ] Isn’t this performance-critical code?
- [ ] Are large types passed as const references?
# ✅ When designing API
- [ ] Does the function name imply an optional return? (try~, maybe~, find~)
- [ ] Is the nullopt return condition specified in the document?
- [ ] Have you considered alternatives (exception, variant, expected)?
- [ ] Is the chaining too deep? (Level 3 or lower recommended)
# ✅ When reviewing code
- Is it appropriate to use [ ] optional<bool>? (Is 3-state necessary?)
- [ ] optional<optional<T>> Is there any overlap?
- [ ] If error information is needed, have you considered the variant?
Practical tips: optional Use effectively
- Express intent with function name
아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ✅ Good name (optional return hint)
std::optional<User> tryFindUser(int id);
std::optional<int> maybeParseInt(const std::string& s);
std::optional<Config> loadConfig(const std::string& path);
// ❌ Ambiguous name
User getUser(int id); // What if there isn't one?
int parseInt(const std::string& s); // What if I fail?
- Based on value_or()
아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ Concise and safe
int port = config.getInt("port").value_or(8080);
// ❌ Wordy
int port;
auto opt = config.getInt("port");
if (opt.has_value()) {
port = opt.value();
} else {
port = 8080;
}
- Using C++23 monadic operations
아래 코드는 cpp를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ✅ Concise with chaining
// 변수 선언 및 초기화
auto result = findUser(id)
.and_then( { return u.getEmail(); })
.transform( { return e.toLowerCase(); })
.value_or("unknown@example.com");
- Use with error logging
아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
auto user = findUser(id);
if (!user) {
LOG_WARNING("User not found: " + std::to_string(id));
return std::nullopt;
}
return user->process();
Quick reference: optional Use decision tree
아래 코드는 code를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
Can there be no value?
├─ Yes → Consider using optional
│ ├─ Error information required? → variant<T, Error> or expected<T, E>
│ ├─ Performance critical? → Consider special values or pointers
│ └─ General case → std::optional ✅
└─ No → Use general type
Troubleshooting: Quick problem solving
| Symptoms | Cause | Solution |
|---|---|---|
| bad_optional_access | No value when calling value() | Use value_or() or check has_value() |
| Compilation error: optional<T&> | Reference type not possible | optional<reference_wrapper |
| poor performance | Copy large type by value | Pass by const reference |
| nested optional | optional<optional | Reexamine design, consider variants |
| chaining complex | too deep and_then | Separate by intermediate variable |
Optional vs other methods performance comparison
| method | memory overhead | Running speed | Safety | Difficulty of use |
|---|---|---|---|---|
| optional | sizeof(T) + 1 to 8 bytes | Fast | High | Easy |
| T* (pointer) | 8 bytes | Very fast | low | middle |
| unique_ptr | 8 bytes + heap allocation | slow | High | middle |
| exception | 0 (only on failure) | very slow | High | Difficulty |
| special value | 0 | Very fast | low | Easy |
Next steps
- Learn how to represent one of several types in std::variant guide
- Learn how to use the two types in practice at Use of Optional and Variant
- Learn the basic concepts of exception handling at Exception Handling Basics
- Learn the selection criteria for exceptions and optional in Exception Handling Guide
- Learn how to use variant instead of inheritance in Polymorphism and Variant
FAQ
Q1: When do you use optional?
A:
- Cases where there may be no value
- Error indication (simple case)
- Null pointer replacement
Q2: optional vs pointer?
A:
- optional: value semantics, safety
- Pointers: reference semantics, risks
Q3: optional vs exception?
A:
- optional: expected failure
- Exception: Exceptional circumstances
Q4: What is the performance overhead?
A: Add one bool flag. You can almost ignore it.
Q5: What is optional<T&>?
A: Impossible. Use optional<reference_wrapper
Q6: What are optional learning resources?
A:
- cppreference.com
- “C++17: The Complete Guide”
- “Effective Modern C++“
Q7: When receiving optional as a function argument, do I need to use a const reference?
A: Pass small types by value and large types by const reference. 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// Small types (int, double, etc.): by value
void process(std::optional<int> opt) {
if (opt) { /* ....*/ }
}
// Large types (string, vector, etc.): as const references
void process(const std::optional<std::string>& opt) {
if (opt) { /* ....*/ }
}
Q8: What are the precautions when using optional as a member variable?
A: Initialize explicitly in the constructor, and use safe access patterns. 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Config {
private:
std::optional<std::string> api_key_;
public:
Config() : api_key_(std::nullopt) {}
std::string getApiKey() const {
return api_key_.value_or("default_key");
}
};
Q9: When do you use optional?
A: Use when three states “true/false/don’t know” are required. 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// User consent status
std::optional<bool> user_consent; // nullopt = don't ask yet
if (!user_consent) {
askForConsent();
} else if (*user_consent) {
proceed();
} else {
showError();
}
Q10: What if optional chaining becomes too deep?
A: Increase readability by separating them into intermediate variables. 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ✅ Separate by intermediate variables
auto user = getUser(id);
if (!user) return std::nullopt;
auto profile = user->getProfile();
if (!profile) return std::nullopt;
return profile->getEmail();
Good article to read together (internal link)
Here’s another article related to this topic.
- C++ variant | “Type-safe union” guide
- C++ std::optional·std::variant complete guide | Type safe instead of nullptr
- C++ exception handling | try/catch/throw “perfect cleanup” [error handling]
- C++ exception handling | try-catch-throw and exceptions vs. error codes, when and what to use
- C++ Modern Polymorphism Design: Composition Variant Instead of Inheritance