C++学习笔记-Optional

std::optional是C++17引入的一个模板类,用于表示一个可能存在也可能不存在的值。它提供了一种类型安全的方式来处理可能为空的情况,避免了使用空指针或特殊值来表示"无值"状态。

传统方法的问题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <iostream>
#include <optional>
#include <fstream>

std::string ReadFileAsString1(const std::string& filepath)
{
    std::ifstream stream(filepath);
    if (stream)
    {
        std::string result;
        // read file
        stream.close();
        return result;
    }
    return std::string(); // 返回空字符串表示失败,但无法区分是真的空文件还是读取失败
}

std::string ReadFileAsString2(const std::string& filepath, bool& outSuccess)
{
    std::ifstream stream(filepath);
    if (stream)
    {
        std::string result;
        // read file
        stream.close();
        outSuccess = true;
        return result;
    }
    outSuccess = false;
    return std::string();
}

std::optional<std::string> ReadFileAsString3(const std::string& filepath)
{
    std::ifstream stream(filepath);
    if (stream)
    {
        std::string result;
        // read file
        stream.close();
        return result;
    }
    return {}; // 失败的情况返回{}即可,这里是一个std::optional{}
}

void TraditionalProblemsDemo()
{
    std::cout << "=== Traditional Methods Problems ===" << std::endl;
    
    // 方法1:无法区分空文件和读取失败
    std::string data1 = ReadFileAsString1("nonexistent.txt");
    if (data1 != "")
    {
        std::cout << "Method 1: File read successfully" << std::endl;
    }
    else
    {
        std::cout << "Method 1: File is empty or read failed (ambiguous)" << std::endl;
    }
    
    // 方法2:需要额外的参数
    bool fileOpenedSuccessfully;
    std::string data2 = ReadFileAsString2("nonexistent.txt", fileOpenedSuccessfully);
    if (fileOpenedSuccessfully)
    {
        std::cout << "Method 2: File read successfully" << std::endl;
    }
    else
    {
        std::cout << "Method 2: File read failed" << std::endl;
    }
    
    // 方法3:使用optional,清晰明确
    std::optional<std::string> data3 = ReadFileAsString3("nonexistent.txt");
    if (data3.has_value()) // 你还可以直接 if(data)
    {
        std::cout << "Method 3: File read successfully: " << data3.value() << std::endl;
    }
    else
    {
        std::cout << "Method 3: File read failed" << std::endl;
    }
}

int main()
{
    TraditionalProblemsDemo();
    return 0;
}

std::optional基本用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include <iostream>
#include <optional>
#include <string>

void BasicOptionalUsage()
{
    std::cout << "=== Basic Optional Usage ===" << std::endl;
    
    // 创建optional
    std::optional<int> opt1;                    // 空的optional
    std::optional<int> opt2 = 42;               // 包含值的optional
    std::optional<int> opt3 = std::nullopt;     // 显式设置为空
    std::optional<int> opt4{100};               // 直接初始化
    
    // 检查是否有值
    std::cout << "opt1 has value: " << opt1.has_value() << std::endl;
    std::cout << "opt2 has value: " << opt2.has_value() << std::endl;
    
    // 使用bool转换
    if (opt2)
    {
        std::cout << "opt2 contains: " << opt2.value() << std::endl;
        // 或者使用解引用操作符
        std::cout << "opt2 contains: " << *opt2 << std::endl;
    }
    
    // 安全访问值
    if (opt1.has_value())
    {
        std::cout << "opt1 value: " << opt1.value() << std::endl;
    }
    else
    {
        std::cout << "opt1 is empty" << std::endl;
    }
    
    // 使用value_or提供默认值
    std::cout << "opt1 value or default: " << opt1.value_or(-1) << std::endl;
    std::cout << "opt2 value or default: " << opt2.value_or(-1) << std::endl;
}

void OptionalWithComplexTypes()
{
    std::cout << "\n=== Optional with Complex Types ===" << std::endl;
    
    // 字符串optional
    std::optional<std::string> optStr;
    optStr = "Hello, Optional!";
    
    if (optStr)
    {
        std::cout << "String: " << *optStr << std::endl;
        std::cout << "Length: " << optStr->length() << std::endl; // 使用箭头操作符
    }
    
    // 重置optional
    optStr.reset();
    std::cout << "After reset, has value: " << optStr.has_value() << std::endl;
    
    // emplace方法
    optStr.emplace("Emplaced string");
    std::cout << "After emplace: " << *optStr << std::endl;
    
    // 自定义类型
    struct Point
    {
        int x, y;
        Point(int x, int y) : x(x), y(y) {}
        void Print() const { std::cout << "(" << x << ", " << y << ")"; }
    };
    
    std::optional<Point> optPoint;
    optPoint.emplace(10, 20);  // 直接构造
    
    if (optPoint)
    {
        std::cout << "Point: ";
        optPoint->Print();
        std::cout << std::endl;
    }
}

int main()
{
    BasicOptionalUsage();
    OptionalWithComplexTypes();
    return 0;
}

实际应用场景

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include <iostream>
#include <optional>
#include <vector>
#include <map>
#include <algorithm>

// 查找函数
std::optional<int> FindElement(const std::vector<int>& vec, int target)
{
    auto it = std::find(vec.begin(), vec.end(), target);
    if (it != vec.end())
    {
        return *it;
    }
    return std::nullopt;
}

// 安全的除法
std::optional<double> SafeDivide(double a, double b)
{
    if (b == 0.0)
    {
        return std::nullopt;
    }
    return a / b;
}

// 解析整数
std::optional<int> ParseInt(const std::string& str)
{
    try
    {
        size_t pos;
        int value = std::stoi(str, &pos);
        
        // 检查是否整个字符串都被解析
        if (pos == str.length())
        {
            return value;
        }
    }
    catch (const std::exception&)
    {
        // 解析失败
    }
    
    return std::nullopt;
}

// 配置管理
class Config
{
private:
    std::map<std::string, std::string> m_Settings;
    
public:
    void Set(const std::string& key, const std::string& value)
    {
        m_Settings[key] = value;
    }
    
    std::optional<std::string> Get(const std::string& key) const
    {
        auto it = m_Settings.find(key);
        if (it != m_Settings.end())
        {
            return it->second;
        }
        return std::nullopt;
    }
    
    std::optional<int> GetInt(const std::string& key) const
    {
        auto value = Get(key);
        if (value)
        {
            return ParseInt(*value);
        }
        return std::nullopt;
    }
    
    std::optional<bool> GetBool(const std::string& key) const
    {
        auto value = Get(key);
        if (value)
        {
            std::string lower = *value;
            std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
            
            if (lower == "true" || lower == "1" || lower == "yes")
                return true;
            else if (lower == "false" || lower == "0" || lower == "no")
                return false;
        }
        return std::nullopt;
    }
};

void PracticalExamples()
{
    std::cout << "=== Practical Examples ===" << std::endl;
    
    // 查找示例
    std::vector<int> numbers{1, 3, 5, 7, 9};
    auto found = FindElement(numbers, 5);
    auto notFound = FindElement(numbers, 6);
    
    std::cout << "Find 5: " << (found ? std::to_string(*found) : "not found") << std::endl;
    std::cout << "Find 6: " << (notFound ? std::to_string(*notFound) : "not found") << std::endl;
    
    // 安全除法示例
    auto result1 = SafeDivide(10.0, 2.0);
    auto result2 = SafeDivide(10.0, 0.0);
    
    std::cout << "10 / 2 = " << (result1 ? std::to_string(*result1) : "undefined") << std::endl;
    std::cout << "10 / 0 = " << (result2 ? std::to_string(*result2) : "undefined") << std::endl;
    
    // 解析示例
    auto parsed1 = ParseInt("123");
    auto parsed2 = ParseInt("abc");
    auto parsed3 = ParseInt("123abc");
    
    std::cout << "Parse '123': " << (parsed1 ? std::to_string(*parsed1) : "failed") << std::endl;
    std::cout << "Parse 'abc': " << (parsed2 ? std::to_string(*parsed2) : "failed") << std::endl;
    std::cout << "Parse '123abc': " << (parsed3 ? std::to_string(*parsed3) : "failed") << std::endl;
    
    // 配置管理示例
    Config config;
    config.Set("port", "8080");
    config.Set("debug", "true");
    config.Set("timeout", "30");
    config.Set("invalid_number", "abc");
    
    std::cout << "\nConfig examples:" << std::endl;
    
    auto port = config.GetInt("port");
    std::cout << "Port: " << (port ? std::to_string(*port) : "not set") << std::endl;
    
    auto debug = config.GetBool("debug");
    std::cout << "Debug: " << (debug ? (*debug ? "enabled" : "disabled") : "not set") << std::endl;
    
    auto timeout = config.GetInt("timeout");
    std::cout << "Timeout: " << (timeout ? std::to_string(*timeout) : "not set") << std::endl;
    
    auto invalid = config.GetInt("invalid_number");
    std::cout << "Invalid number: " << (invalid ? std::to_string(*invalid) : "invalid") << std::endl;
    
    auto missing = config.Get("missing_key");
    std::cout << "Missing key: " << (missing ? *missing : "not found") << std::endl;
}

int main()
{
    PracticalExamples();
    return 0;
}

链式操作和函数式编程

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#include <iostream>
#include <optional>
#include <functional>
#include <vector>

// 链式操作辅助函数
template<typename T, typename F>
auto and_then(const std::optional<T>& opt, F&& func) -> decltype(func(*opt))
{
    if (opt)
    {
        return func(*opt);
    }
    return {};
}

template<typename T, typename F>
auto transform(const std::optional<T>& opt, F&& func) -> std::optional<decltype(func(*opt))>
{
    if (opt)
    {
        return func(*opt);
    }
    return {};
}

// 示例函数
std::optional<int> StringToInt(const std::string& str)
{
    try
    {
        return std::stoi(str);
    }
    catch (...)
    {
        return std::nullopt;
    }
}

std::optional<int> Square(int x)
{
    return x * x;
}

std::optional<std::string> IntToString(int x)
{
    return std::to_string(x);
}

void ChainedOperationsDemo()
{
    std::cout << "=== Chained Operations Demo ===" << std::endl;
    
    // 传统方式
    std::string input = "5";
    std::optional<std::string> result;
    
    auto maybeInt = StringToInt(input);
    if (maybeInt)
    {
        auto maybeSquared = Square(*maybeInt);
        if (maybeSquared)
        {
            result = IntToString(*maybeSquared);
        }
    }
    
    std::cout << "Traditional way: " << (result ? *result : "failed") << std::endl;
    
    // 链式操作方式
    auto chainedResult = and_then(StringToInt(input), [](int x) {
        return and_then(Square(x), [](int squared) {
            return IntToString(squared);
        });
    });
    
    std::cout << "Chained way: " << (chainedResult ? *chainedResult : "failed") << std::endl;
    
    // 使用transform
    auto transformResult = transform(
        transform(StringToInt(input), [](int x) { return x * x; }),
        [](int squared) { return std::to_string(squared); }
    );
    
    std::cout << "Transform way: " << (transformResult ? *transformResult : "failed") << std::endl;
}

// 实际应用:用户数据处理
struct User
{
    int id;
    std::string name;
    std::string email;
};

class UserDatabase
{
private:
    std::vector<User> m_Users{
        {1, "Alice", "alice@example.com"},
        {2, "Bob", "bob@example.com"},
        {3, "Charlie", "charlie@example.com"}
    };
    
public:
    std::optional<User> FindUserById(int id) const
    {
        auto it = std::find_if(m_Users.begin(), m_Users.end(),
                              [id](const User& user) { return user.id == id; });
        
        if (it != m_Users.end())
        {
            return *it;
        }
        return std::nullopt;
    }
    
    std::optional<std::string> GetUserEmail(int id) const
    {
        auto user = FindUserById(id);
        if (user)
        {
            return user->email;
        }
        return std::nullopt;
    }
    
    std::optional<std::string> GetUserDomain(int id) const
    {
        auto email = GetUserEmail(id);
        if (email)
        {
            size_t atPos = email->find('@');
            if (atPos != std::string::npos && atPos + 1 < email->length())
            {
                return email->substr(atPos + 1);
            }
        }
        return std::nullopt;
    }
};

void UserDataProcessingDemo()
{
    std::cout << "\n=== User Data Processing Demo ===" << std::endl;
    
    UserDatabase db;
    
    // 查找用户
    auto user1 = db.FindUserById(1);
    auto user999 = db.FindUserById(999);
    
    std::cout << "User 1: " << (user1 ? user1->name : "not found") << std::endl;
    std::cout << "User 999: " << (user999 ? user999->name : "not found") << std::endl;
    
    // 获取邮箱域名
    for (int id : {1, 2, 3, 999})
    {
        auto domain = db.GetUserDomain(id);
        std::cout << "User " << id << " domain: " << (domain ? *domain : "not found") << std::endl;
    }
}

int main()
{
    ChainedOperationsDemo();
    UserDataProcessingDemo();
    return 0;
}

性能考虑和最佳实践

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#include <iostream>
#include <optional>
#include <chrono>
#include <vector>

// 性能测试
void PerformanceComparison()
{
    std::cout << "=== Performance Comparison ===" << std::endl;
    
    const int iterations = 1000000;
    
    // 测试1:使用指针
    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < iterations; ++i)
    {
        int* ptr = (i % 2 == 0) ? new int(i) : nullptr;
        if (ptr)
        {
            volatile int value = *ptr;
            delete ptr;
        }
    }
    auto end = std::chrono::high_resolution_clock::now();
    auto pointerTime = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    
    // 测试2:使用optional
    start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < iterations; ++i)
    {
        std::optional<int> opt = (i % 2 == 0) ? std::optional<int>(i) : std::nullopt;
        if (opt)
        {
            volatile int value = *opt;
        }
    }
    end = std::chrono::high_resolution_clock::now();
    auto optionalTime = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    
    std::cout << "Pointer approach: " << pointerTime.count() << " ms" << std::endl;
    std::cout << "Optional approach: " << optionalTime.count() << " ms" << std::endl;
    std::cout << "Optional is " << (double)pointerTime.count() / optionalTime.count() << "x faster" << std::endl;
}

// 最佳实践示例
class BestPracticesDemo
{
public:
    // 好的做法:返回optional而不是指针
    static std::optional<int> FindMax(const std::vector<int>& vec)
    {
        if (vec.empty())
            return std::nullopt;
        
        return *std::max_element(vec.begin(), vec.end());
    }
    
    // 好的做法:使用optional作为可选参数的替代
    static void ProcessData(const std::string& data, 
                           std::optional<int> maxLength = std::nullopt,
                           std::optional<bool> caseSensitive = std::nullopt)
    {
        std::cout << "Processing: " << data << std::endl;
        
        if (maxLength)
        {
            std::cout << "  Max length: " << *maxLength << std::endl;
        }
        
        if (caseSensitive)
        {
            std::cout << "  Case sensitive: " << (*caseSensitive ? "yes" : "no") << std::endl;
        }
    }
    
    // 好的做法:链式调用
    static std::optional<std::string> ProcessString(const std::string& input)
    {
        if (input.empty())
            return std::nullopt;
        
        // 一系列可能失败的操作
        if (input.length() < 3)
            return std::nullopt;
        
        return input.substr(0, 3) + "...";
    }
    
    // 避免的做法:不要过度使用optional
    // 如果值总是存在,不要使用optional
    static int AlwaysReturnsValue(int x)
    {
        return x * 2;  // 不要返回 std::optional<int>
    }
};

void BestPracticesExamples()
{
    std::cout << "\n=== Best Practices Examples ===" << std::endl;
    
    // 查找最大值
    std::vector<int> numbers{3, 1, 4, 1, 5, 9};
    std::vector<int> empty;
    
    auto max1 = BestPracticesDemo::FindMax(numbers);
    auto max2 = BestPracticesDemo::FindMax(empty);
    
    std::cout << "Max of numbers: " << (max1 ? std::to_string(*max1) : "none") << std::endl;
    std::cout << "Max of empty: " << (max2 ? std::to_string(*max2) : "none") << std::endl;
    
    // 可选参数
    BestPracticesDemo::ProcessData("test data");
    BestPracticesDemo::ProcessData("test data", 10);
    BestPracticesDemo::ProcessData("test data", 10, true);
    BestPracticesDemo::ProcessData("test data", std::nullopt, false);
    
    // 字符串处理
    auto result1 = BestPracticesDemo::ProcessString("Hello World");
    auto result2 = BestPracticesDemo::ProcessString("Hi");
    auto result3 = BestPracticesDemo::ProcessString("");
    
    std::cout << "Process 'Hello World': " << (result1 ? *result1 : "failed") << std::endl;
    std::cout << "Process 'Hi': " << (result2 ? *result2 : "failed") << std::endl;
    std::cout << "Process '': " << (result3 ? *result3 : "failed") << std::endl;
}

// 错误处理模式
enum class ErrorCode
{
    Success,
    FileNotFound,
    PermissionDenied,
    InvalidFormat
};

template<typename T>
class Result
{
private:
    std::optional<T> m_Value;
    ErrorCode m_Error;
    
public:
    Result(T value) : m_Value(std::move(value)), m_Error(ErrorCode::Success) {}
    Result(ErrorCode error) : m_Error(error) {}
    
    bool IsSuccess() const { return m_Value.has_value(); }
    const T& Value() const { return *m_Value; }
    ErrorCode Error() const { return m_Error; }
    
    explicit operator bool() const { return IsSuccess(); }
};

Result<std::string> ReadConfigFile(const std::string& filename)
{
    if (filename.empty())
        return ErrorCode::InvalidFormat;
    
    if (filename == "missing.conf")
        return ErrorCode::FileNotFound;
    
    if (filename == "protected.conf")
        return ErrorCode::PermissionDenied;
    
    return std::string("config data from " + filename);
}

void ErrorHandlingDemo()
{
    std::cout << "\n=== Error Handling Demo ===" << std::endl;
    
    std::vector<std::string> files{"config.conf", "missing.conf", "protected.conf", ""};
    
    for (const auto& file : files)
    {
        auto result = ReadConfigFile(file);
        if (result)
        {
            std::cout << "Success: " << result.Value() << std::endl;
        }
        else
        {
            std::cout << "Error reading " << file << ": ";
            switch (result.Error())
            {
                case ErrorCode::FileNotFound:
                    std::cout << "File not found";
                    break;
                case ErrorCode::PermissionDenied:
                    std::cout << "Permission denied";
                    break;
                case ErrorCode::InvalidFormat:
                    std::cout << "Invalid format";
                    break;
                default:
                    std::cout << "Unknown error";
            }
            std::cout << std::endl;
        }
    }
}

int main()
{
    PerformanceComparison();
    BestPracticesExamples();
    ErrorHandlingDemo();
    
    return 0;
}

总结

  1. std::optional优势:类型安全地表示可能不存在的值,避免空指针和特殊值
  2. 基本用法
    • 创建:std::optional<T> opt = value;std::nullopt
    • 检查:opt.has_value()if (opt)
    • 访问:opt.value()*opt
    • 默认值:opt.value_or(default)
  3. 实际应用
    • 函数可能失败的返回值
    • 配置项的可选设置
    • 数据库查询结果
    • 解析操作的结果
  4. 性能特点
    • 栈分配,无动态内存开销
    • 通常比指针方法更快
    • 编译器优化友好
  5. 最佳实践
    • 替代可能为空的指针
    • 用于可选参数
    • 链式操作处理
    • 避免过度使用(值总是存在时不要用)
  6. 与其他方案对比
    • 比空指针更安全
    • 比异常处理更轻量
    • 比特殊值更明确
    • 支持函数式编程风格
updatedupdated2025-09-202025-09-20