C++学习笔记-Variant

std::variant是C++17引入的类型安全的联合体,可以在运行时存储多种类型中的一种。与传统的union相比,variant提供了类型安全、异常安全和更好的接口,是一个类型安全的Union。

基本用法

 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
#include <iostream>
#include <variant>
#include <string>

int main()
{
    std::cout << "=== Basic Variant Usage ===" << std::endl;
    
    std::variant<std::string, int> data;
    data = "Jerry";
    std::cout << "String value: " << std::get<std::string>(data) << std::endl;
    
    data = 2;
    std::cout << "Int value: " << std::get<int>(data) << std::endl;

    /* 检查判断类型是否匹配 */
    if (data.index() != 1) // 索引从0开始,1表示第二个int
        std::cout << "data not int" << std::endl;

    // 第二种方式:std::get_if
    auto value = std::get_if<int>(&data);
    if (!value)
        std::cout << "data not int" << std::endl;
    else
        std::cout << "data is int: " << *value << std::endl;

    // Variant 是一个类型安全的Union
    std::cout << "sizeof(int): " << sizeof(int) << std::endl;
    std::cout << "sizeof(std::string): " << sizeof(std::string) << std::endl;
    std::cout << "sizeof(variant): " << sizeof(data) << std::endl;
    
    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
#include <iostream>
#include <variant>
#include <string>
#include <vector>

void TypeCheckingDemo()
{
    std::cout << "=== Type Checking Demo ===" << std::endl;
    
    std::variant<int, double, std::string> var;
    
    // 设置不同类型的值
    std::vector<std::variant<int, double, std::string>> values{
        42,
        3.14,
        std::string("Hello"),
        100,
        2.718
    };
    
    for (size_t i = 0; i < values.size(); ++i)
    {
        auto& v = values[i];
        std::cout << "Value " << i << ": ";
        
        // 方法1:使用index()检查类型
        switch (v.index())
        {
            case 0:
                std::cout << "int = " << std::get<int>(v);
                break;
            case 1:
                std::cout << "double = " << std::get<double>(v);
                break;
            case 2:
                std::cout << "string = " << std::get<std::string>(v);
                break;
        }
        std::cout << std::endl;
    }
    
    std::cout << "\nUsing std::holds_alternative:" << std::endl;
    for (size_t i = 0; i < values.size(); ++i)
    {
        auto& v = values[i];
        std::cout << "Value " << i << ": ";
        
        // 方法2:使用std::holds_alternative
        if (std::holds_alternative<int>(v))
        {
            std::cout << "int = " << std::get<int>(v);
        }
        else if (std::holds_alternative<double>(v))
        {
            std::cout << "double = " << std::get<double>(v);
        }
        else if (std::holds_alternative<std::string>(v))
        {
            std::cout << "string = " << std::get<std::string>(v);
        }
        std::cout << std::endl;
    }
    
    std::cout << "\nUsing std::get_if:" << std::endl;
    for (size_t i = 0; i < values.size(); ++i)
    {
        auto& v = values[i];
        std::cout << "Value " << i << ": ";
        
        // 方法3:使用std::get_if(安全访问)
        if (auto* intPtr = std::get_if<int>(&v))
        {
            std::cout << "int = " << *intPtr;
        }
        else if (auto* doublePtr = std::get_if<double>(&v))
        {
            std::cout << "double = " << *doublePtr;
        }
        else if (auto* stringPtr = std::get_if<std::string>(&v))
        {
            std::cout << "string = " << *stringPtr;
        }
        std::cout << std::endl;
    }
}

int main()
{
    TypeCheckingDemo();
    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
#include <iostream>
#include <variant>
#include <string>
#include <vector>

// 访问者函数对象
struct PrintVisitor
{
    void operator()(int value) const
    {
        std::cout << "Integer: " << value;
    }
    
    void operator()(double value) const
    {
        std::cout << "Double: " << value;
    }
    
    void operator()(const std::string& value) const
    {
        std::cout << "String: " << value;
    }
};

// 泛型访问者
struct GenericVisitor
{
    template<typename T>
    void operator()(const T& value) const
    {
        std::cout << "Generic: " << value << " (type: " << typeid(T).name() << ")";
    }
};

// 返回值的访问者
struct SizeVisitor
{
    size_t operator()(int value) const
    {
        return sizeof(int);
    }
    
    size_t operator()(double value) const
    {
        return sizeof(double);
    }
    
    size_t operator()(const std::string& value) const
    {
        return value.length();
    }
};

void VisitorPatternDemo()
{
    std::cout << "=== Visitor Pattern Demo ===" << std::endl;
    
    std::vector<std::variant<int, double, std::string>> values{
        42,
        3.14159,
        std::string("Hello World"),
        -100,
        2.718
    };
    
    std::cout << "Using custom visitor:" << std::endl;
    for (size_t i = 0; i < values.size(); ++i)
    {
        std::cout << "Value " << i << ": ";
        std::visit(PrintVisitor{}, values[i]);
        std::cout << std::endl;
    }
    
    std::cout << "\nUsing generic visitor:" << std::endl;
    for (size_t i = 0; i < values.size(); ++i)
    {
        std::cout << "Value " << i << ": ";
        std::visit(GenericVisitor{}, values[i]);
        std::cout << std::endl;
    }
    
    std::cout << "\nUsing lambda visitor:" << std::endl;
    for (size_t i = 0; i < values.size(); ++i)
    {
        std::cout << "Value " << i << ": ";
        std::visit([](const auto& value) {
            std::cout << "Lambda: " << value;
        }, values[i]);
        std::cout << std::endl;
    }
    
    std::cout << "\nUsing size visitor:" << std::endl;
    for (size_t i = 0; i < values.size(); ++i)
    {
        auto size = std::visit(SizeVisitor{}, values[i]);
        std::cout << "Value " << i << " size: " << size << std::endl;
    }
}

int main()
{
    VisitorPatternDemo();
    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
#include <iostream>
#include <variant>
#include <string>
#include <vector>
#include <memory>

// 表达式求值器
struct Number
{
    double value;
    Number(double v) : value(v) {}
};

struct BinaryOp
{
    char op;
    std::unique_ptr<struct Expression> left;
    std::unique_ptr<struct Expression> right;
    
    BinaryOp(char o, std::unique_ptr<Expression> l, std::unique_ptr<Expression> r)
        : op(o), left(std::move(l)), right(std::move(r)) {}
};

struct Expression
{
    std::variant<Number, BinaryOp> data;
    
    Expression(Number n) : data(std::move(n)) {}
    Expression(BinaryOp op) : data(std::move(op)) {}
};

// 求值访问者
struct EvaluateVisitor
{
    double operator()(const Number& num) const
    {
        return num.value;
    }
    
    double operator()(const BinaryOp& op) const
    {
        double leftVal = std::visit(*this, op.left->data);
        double rightVal = std::visit(*this, op.right->data);
        
        switch (op.op)
        {
            case '+': return leftVal + rightVal;
            case '-': return leftVal - rightVal;
            case '*': return leftVal * rightVal;
            case '/': return rightVal != 0 ? leftVal / rightVal : 0;
            default: return 0;
        }
    }
};

// 打印访问者
struct PrintVisitor
{
    void operator()(const Number& num) const
    {
        std::cout << num.value;
    }
    
    void operator()(const BinaryOp& op) const
    {
        std::cout << "(";
        std::visit(*this, op.left->data);
        std::cout << " " << op.op << " ";
        std::visit(*this, op.right->data);
        std::cout << ")";
    }
};

// 错误处理
enum class ErrorType
{
    None,
    FileNotFound,
    PermissionDenied,
    InvalidFormat,
    NetworkError
};

template<typename T>
using Result = std::variant<T, ErrorType>;

Result<std::string> ReadFile(const std::string& filename)
{
    if (filename.empty())
        return ErrorType::InvalidFormat;
    
    if (filename == "missing.txt")
        return ErrorType::FileNotFound;
    
    if (filename == "protected.txt")
        return ErrorType::PermissionDenied;
    
    return std::string("Content of " + filename);
}

Result<int> ParseNumber(const std::string& str)
{
    try
    {
        return std::stoi(str);
    }
    catch (...)
    {
        return ErrorType::InvalidFormat;
    }
}

void PracticalExamplesDemo()
{
    std::cout << "=== Practical Examples Demo ===" << std::endl;
    
    // 表达式求值
    std::cout << "Expression evaluation:" << std::endl;
    
    // 创建表达式: (3 + 4) * 2
    auto expr = Expression(BinaryOp('*',
        std::make_unique<Expression>(BinaryOp('+',
            std::make_unique<Expression>(Number(3)),
            std::make_unique<Expression>(Number(4))
        )),
        std::make_unique<Expression>(Number(2))
    ));
    
    std::cout << "Expression: ";
    std::visit(PrintVisitor{}, expr.data);
    std::cout << " = " << std::visit(EvaluateVisitor{}, expr.data) << std::endl;
    
    // 错误处理
    std::cout << "\nError handling with variant:" << std::endl;
    
    std::vector<std::string> files{"data.txt", "missing.txt", "protected.txt", ""};
    
    for (const auto& file : files)
    {
        auto result = ReadFile(file);
        
        std::visit([&file](const auto& value) {
            using T = std::decay_t<decltype(value)>;
            if constexpr (std::is_same_v<T, std::string>)
            {
                std::cout << "Success reading " << file << ": " << value << std::endl;
            }
            else if constexpr (std::is_same_v<T, ErrorType>)
            {
                std::cout << "Error reading " << file << ": ";
                switch (value)
                {
                    case ErrorType::FileNotFound:
                        std::cout << "File not found";
                        break;
                    case ErrorType::PermissionDenied:
                        std::cout << "Permission denied";
                        break;
                    case ErrorType::InvalidFormat:
                        std::cout << "Invalid format";
                        break;
                    default:
                        std::cout << "Unknown error";
                }
                std::cout << std::endl;
            }
        }, result);
    }
    
    // 数字解析
    std::cout << "\nNumber parsing:" << std::endl;
    std::vector<std::string> numbers{"123", "abc", "456", "12.34"};
    
    for (const auto& numStr : numbers)
    {
        auto result = ParseNumber(numStr);
        
        if (std::holds_alternative<int>(result))
        {
            std::cout << "Parsed '" << numStr << "': " << std::get<int>(result) << std::endl;
        }
        else
        {
            std::cout << "Failed to parse '" << numStr << "'" << std::endl;
        }
    }
}

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

与union的比较

  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
#include <iostream>
#include <variant>
#include <string>

// 传统union
union TraditionalUnion
{
    int intValue;
    double doubleValue;
    // char* stringValue; // 不能包含非平凡类型
};

// 带标记的union
enum class UnionType { INT, DOUBLE, STRING };

struct TaggedUnion
{
    UnionType type;
    union
    {
        int intValue;
        double doubleValue;
        char stringValue[32]; // 固定大小
    };
    
    TaggedUnion(int value) : type(UnionType::INT), intValue(value) {}
    TaggedUnion(double value) : type(UnionType::DOUBLE), doubleValue(value) {}
    TaggedUnion(const char* value) : type(UnionType::STRING)
    {
        strncpy(stringValue, value, 31);
        stringValue[31] = '\0';
    }
    
    void Print() const
    {
        switch (type)
        {
            case UnionType::INT:
                std::cout << "int: " << intValue;
                break;
            case UnionType::DOUBLE:
                std::cout << "double: " << doubleValue;
                break;
            case UnionType::STRING:
                std::cout << "string: " << stringValue;
                break;
        }
    }
};

void ComparisonDemo()
{
    std::cout << "=== Union vs Variant Comparison ===" << std::endl;
    
    // 传统union的问题
    std::cout << "Traditional union problems:" << std::endl;
    TraditionalUnion tu;
    tu.intValue = 42;
    std::cout << "Set int: " << tu.intValue << std::endl;
    
    tu.doubleValue = 3.14;
    std::cout << "Set double: " << tu.doubleValue << std::endl;
    std::cout << "Int value after setting double: " << tu.intValue << " (corrupted)" << std::endl;
    
    // 带标记的union
    std::cout << "\nTagged union:" << std::endl;
    TaggedUnion tagged1(42);
    TaggedUnion tagged2(3.14);
    TaggedUnion tagged3("Hello");
    
    tagged1.Print(); std::cout << std::endl;
    tagged2.Print(); std::cout << std::endl;
    tagged3.Print(); std::cout << std::endl;
    
    // std::variant的优势
    std::cout << "\nstd::variant advantages:" << std::endl;
    std::variant<int, double, std::string> var1 = 42;
    std::variant<int, double, std::string> var2 = 3.14;
    std::variant<int, double, std::string> var3 = std::string("Hello World");
    
    auto printVariant = [](const auto& var) {
        std::visit([](const auto& value) {
            std::cout << "variant: " << value << " (type: " << typeid(value).name() << ")";
        }, var);
        std::cout << std::endl;
    };
    
    printVariant(var1);
    printVariant(var2);
    printVariant(var3);
    
    // 大小比较
    std::cout << "\nSize comparison:" << std::endl;
    std::cout << "TraditionalUnion: " << sizeof(TraditionalUnion) << " bytes" << std::endl;
    std::cout << "TaggedUnion: " << sizeof(TaggedUnion) << " bytes" << std::endl;
    std::cout << "std::variant: " << sizeof(var1) << " bytes" << std::endl;
    
    std::cout << "\nVariant advantages:" << std::endl;
    std::cout << "- Type safety" << std::endl;
    std::cout << "- Exception safety" << std::endl;
    std::cout << "- Supports complex types (std::string, etc.)" << std::endl;
    std::cout << "- Visitor pattern support" << std::endl;
    std::cout << "- Runtime type information" << std::endl;
    std::cout << "- Automatic memory management" << std::endl;
}

int main()
{
    ComparisonDemo();
    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
#include <iostream>
#include <variant>
#include <string>
#include <vector>
#include <optional>

// 递归variant(需要前向声明)
struct JsonValue;
using JsonArray = std::vector<JsonValue>;
using JsonObject = std::map<std::string, JsonValue>;

struct JsonValue
{
    std::variant<
        std::nullptr_t,
        bool,
        int,
        double,
        std::string,
        JsonArray,
        JsonObject
    > data;
    
    JsonValue() : data(nullptr) {}
    JsonValue(bool b) : data(b) {}
    JsonValue(int i) : data(i) {}
    JsonValue(double d) : data(d) {}
    JsonValue(const std::string& s) : data(s) {}
    JsonValue(const JsonArray& a) : data(a) {}
    JsonValue(const JsonObject& o) : data(o) {}
};

// JSON打印访问者
struct JsonPrintVisitor
{
    void operator()(std::nullptr_t) const
    {
        std::cout << "null";
    }
    
    void operator()(bool value) const
    {
        std::cout << (value ? "true" : "false");
    }
    
    void operator()(int value) const
    {
        std::cout << value;
    }
    
    void operator()(double value) const
    {
        std::cout << value;
    }
    
    void operator()(const std::string& value) const
    {
        std::cout << "\"" << value << "\"";
    }
    
    void operator()(const JsonArray& array) const
    {
        std::cout << "[";
        for (size_t i = 0; i < array.size(); ++i)
        {
            if (i > 0) std::cout << ", ";
            std::visit(*this, array[i].data);
        }
        std::cout << "]";
    }
    
    void operator()(const JsonObject& object) const
    {
        std::cout << "{";
        bool first = true;
        for (const auto& [key, value] : object)
        {
            if (!first) std::cout << ", ";
            first = false;
            std::cout << "\"" << key << "\": ";
            std::visit(*this, value.data);
        }
        std::cout << "}";
    }
};

// 异常安全的variant操作
template<typename... Types>
class SafeVariant
{
private:
    std::variant<Types...> m_Data;
    
public:
    template<typename T>
    SafeVariant(T&& value) : m_Data(std::forward<T>(value)) {}
    
    template<typename T>
    std::optional<T> Get() const
    {
        if (std::holds_alternative<T>(m_Data))
        {
            return std::get<T>(m_Data);
        }
        return std::nullopt;
    }
    
    template<typename Visitor>
    auto Visit(Visitor&& visitor) const
    {
        return std::visit(std::forward<Visitor>(visitor), m_Data);
    }
    
    size_t Index() const { return m_Data.index(); }
    
    template<typename T>
    bool Is() const { return std::holds_alternative<T>(m_Data); }
};

void AdvancedFeaturesDemo()
{
    std::cout << "=== Advanced Features Demo ===" << std::endl;
    
    // JSON示例
    JsonValue json = JsonObject{
        {"name", JsonValue("John Doe")},
        {"age", JsonValue(30)},
        {"active", JsonValue(true)},
        {"scores", JsonValue(JsonArray{
            JsonValue(95),
            JsonValue(87),
            JsonValue(92)
        })},
        {"address", JsonValue(JsonObject{
            {"street", JsonValue("123 Main St")},
            {"city", JsonValue("Anytown")}
        })}
    };
    
    std::cout << "JSON: ";
    std::visit(JsonPrintVisitor{}, json.data);
    std::cout << std::endl;
    
    // 安全variant示例
    std::cout << "\nSafe variant demo:" << std::endl;
    SafeVariant<int, double, std::string> safeVar(42);
    
    auto intValue = safeVar.Get<int>();
    auto stringValue = safeVar.Get<std::string>();
    
    std::cout << "Int value: " << (intValue ? std::to_string(*intValue) : "none") << std::endl;
    std::cout << "String value: " << (stringValue ? *stringValue : "none") << std::endl;
    
    // 类型检查
    std::cout << "Is int: " << safeVar.Is<int>() << std::endl;
    std::cout << "Is string: " << safeVar.Is<std::string>() << std::endl;
    
    // 访问者模式
    safeVar.Visit([](const auto& value) {
        std::cout << "Safe variant contains: " << value << std::endl;
    });
}

void BestPracticesDemo()
{
    std::cout << "\n=== Best Practices ===" << std::endl;
    
    std::cout << "1. Use std::variant instead of union for type safety" << std::endl;
    std::cout << "2. Prefer std::visit over manual type checking" << std::endl;
    std::cout << "3. Use std::holds_alternative for simple type checks" << std::endl;
    std::cout << "4. Consider std::optional for nullable types" << std::endl;
    std::cout << "5. Use generic lambdas for simple visitors" << std::endl;
    std::cout << "6. Be careful with recursive variants (use pointers/smart pointers)" << std::endl;
    std::cout << "7. Consider performance implications of large variants" << std::endl;
}

int main()
{
    AdvancedFeaturesDemo();
    BestPracticesDemo();
    return 0;
}

总结

  1. std::variant基础:C++17的类型安全联合体,可以存储多种类型中的一种
  2. 类型安全
    • 运行时类型信息
    • 异常安全的类型转换
    • 自动内存管理
  3. 访问方法
    • std::get<T>():直接获取值(可能抛异常)
    • std::get_if<T>():安全获取指针
    • std::holds_alternative<T>():类型检查
    • std::visit():访问者模式
  4. 实际应用
    • 错误处理和结果类型
    • 表达式求值器
    • JSON数据结构
    • 状态机实现
  5. 与union对比
    • 类型安全 vs 手动类型管理
    • 支持复杂类型 vs 仅POD类型
    • 异常安全 vs 手动资源管理
    • 更大的内存开销 vs 最小内存使用
  6. 最佳实践
    • 优先使用variant而非union
    • 使用访问者模式处理所有类型
    • 考虑性能影响(variant有额外开销)
    • 递归类型需要使用指针或智能指针
updatedupdated2025-09-202025-09-20