跳到主要内容

C++ 11非受限联合

什么是非受限联合?

在C++11之前,联合体(union)有许多限制,其中最显著的是它们只能包含POD(Plain Old Data)类型的成员。简单来说,联合体成员不能是有非平凡构造函数、析构函数、拷贝构造函数、拷贝赋值运算符的类型,也不能有虚函数或虚继承。

C++11引入了**非受限联合(Unrestricted Unions)**特性,放宽了这些限制,允许联合体包含更复杂的类型,如具有构造函数、析构函数等的非POD类型。这极大地扩展了联合体的使用场景。

备注

联合体(union)是一种特殊的类,它可以持有多个不同类型的数据成员,但在任何时刻只能有一个成员有值。

传统联合体的限制

在C++11之前,如果我们尝试在联合体中使用非POD类型(如std::string),编译器会报错:

cpp
// C++98/03中,以下代码无法编译
union OldUnion {
int i;
float f;
std::string s; // 错误:非POD类型不允许作为联合体成员
};

C++ 11非受限联合的新特性

C++11中,我们可以在联合体中使用具有构造函数、析构函数、拷贝/移动构造、拷贝/移动赋值的类型:

cpp
#include <iostream>
#include <string>
#include <vector>

// C++11中,以下代码可以编译
union ModernUnion {
int i;
float f;
std::string s; // C++11允许:非POD类型作为联合体成员

// 需要显式定义构造和析构函数
ModernUnion() : i(0) {} // 默认初始化int成员
~ModernUnion() {}
};

但是,使用非受限联合体时,我们需要注意以下几点:

1. 手动构造和析构

当联合体包含带有构造函数或析构函数的成员时,编译器不会自动调用它们。我们需要使用定位new(placement new)来手动构造,以及显式调用析构函数来销毁:

cpp
#include <iostream>
#include <string>
#include <new> // 为了使用placement new

union Value {
int i;
float f;
std::string s;

Value() : i(0) {} // 默认初始化int成员
~Value() {} // 空析构函数,我们会手动调用成员的析构函数
};

int main() {
Value v;

// 构造string成员
new (&v.s) std::string("Hello, C++11!");

std::cout << "String value: " << v.s << std::endl;

// 使用完毕后,手动调用析构函数
v.s.~basic_string();

// 现在可以使用其他成员
v.i = 42;
std::cout << "Integer value: " << v.i << std::endl;

return 0;
}

输出:

String value: Hello, C++11!
Integer value: 42

2. 活跃成员跟踪

使用非受限联合体时,我们需要手动跟踪当前"活跃"(即有效值)的成员。这通常通过一个额外的枚举变量来实现:

cpp
#include <iostream>
#include <string>
#include <new>

enum class ValueType {
INT,
FLOAT,
STRING
};

union Value {
int i;
float f;
std::string s;

Value() : i(0) {}
~Value() {}
};

class VariantValue {
private:
Value val;
ValueType type;

public:
VariantValue() : type(ValueType::INT) {
new (&val.i) int(0);
}

~VariantValue() {
destroyValue();
}

void setInt(int i) {
destroyValue();
new (&val.i) int(i);
type = ValueType::INT;
}

void setFloat(float f) {
destroyValue();
new (&val.f) float(f);
type = ValueType::FLOAT;
}

void setString(const std::string& s) {
destroyValue();
new (&val.s) std::string(s);
type = ValueType::STRING;
}

void print() const {
switch (type) {
case ValueType::INT:
std::cout << "Int value: " << val.i << std::endl;
break;
case ValueType::FLOAT:
std::cout << "Float value: " << val.f << std::endl;
break;
case ValueType::STRING:
std::cout << "String value: " << val.s << std::endl;
break;
}
}

private:
void destroyValue() {
switch (type) {
case ValueType::STRING:
val.s.~basic_string();
break;
default:
break; // int和float不需要手动销毁
}
}
};

int main() {
VariantValue value;

value.setInt(42);
value.print();

value.setFloat(3.14f);
value.print();

value.setString("C++11非受限联合");
value.print();

return 0;
}

输出:

Int value: 42
Float value: 3.14
String value: C++11非受限联合

非受限联合的实际应用场景

1. 实现变体类型(Variant)

非受限联合是实现类似std::variant(C++17引入)这样的变体类型的基础。变体类型可以持有多种可能的类型之一,非常适合用于状态机、解析器等场景:

cpp
#include <iostream>
#include <string>
#include <memory>

// 简化版的变体类型实现
class SimpleVariant {
private:
enum class Type {
INTEGER,
STRING,
DOUBLE
};

union Data {
int intValue;
std::string stringValue;
double doubleValue;

Data() : intValue(0) {}
~Data() {} // 需要手动析构
};

Type currentType;
Data data;

public:
SimpleVariant() : currentType(Type::INTEGER) {
new (&data.intValue) int(0);
}

~SimpleVariant() {
clear();
}

void setInt(int value) {
clear();
new (&data.intValue) int(value);
currentType = Type::INTEGER;
}

void setString(const std::string& value) {
clear();
new (&data.stringValue) std::string(value);
currentType = Type::STRING;
}

void setDouble(double value) {
clear();
new (&data.doubleValue) double(value);
currentType = Type::DOUBLE;
}

void clear() {
if (currentType == Type::STRING) {
data.stringValue.~basic_string();
}
currentType = Type::INTEGER;
data.intValue = 0;
}

void printValue() const {
switch (currentType) {
case Type::INTEGER:
std::cout << "Integer: " << data.intValue << std::endl;
break;
case Type::STRING:
std::cout << "String: " << data.stringValue << std::endl;
break;
case Type::DOUBLE:
std::cout << "Double: " << data.doubleValue << std::endl;
break;
}
}
};

int main() {
SimpleVariant variant;

variant.setInt(42);
variant.printValue();

variant.setString("Hello, Variant!");
variant.printValue();

variant.setDouble(3.14159);
variant.printValue();

return 0;
}

输出:

Integer: 42
String: Hello, Variant!
Double: 3.14159

2. 优化内存使用

在资源受限的环境下,非受限联合体可以帮助我们优化内存使用,特别是当我们需要存储多种可能的类型,但在任何时刻只需要其中一种时:

cpp
#include <iostream>
#include <string>
#include <vector>

// 模拟JSON值类型
class JsonValue {
public:
enum class Type {
NULL_VALUE,
BOOLEAN,
NUMBER,
STRING,
ARRAY,
OBJECT
};

private:
union Data {
bool boolValue;
double numberValue;
std::string stringValue;
std::vector<JsonValue>* arrayValue;
// 在实际实现中,这里还会有对象类型

Data() : numberValue(0) {}
~Data() {}
};

Type type;
Data data;

public:
JsonValue() : type(Type::NULL_VALUE) {}

JsonValue(bool value) : type(Type::BOOLEAN) {
data.boolValue = value;
}

JsonValue(double value) : type(Type::NUMBER) {
data.numberValue = value;
}

JsonValue(const std::string& value) : type(Type::STRING) {
new (&data.stringValue) std::string(value);
}

JsonValue(const JsonValue& other) : type(Type::NULL_VALUE) {
*this = other;
}

~JsonValue() {
clear();
}

JsonValue& operator=(const JsonValue& other) {
if (this != &other) {
clear();
type = other.type;

switch (type) {
case Type::BOOLEAN:
data.boolValue = other.data.boolValue;
break;
case Type::NUMBER:
data.numberValue = other.data.numberValue;
break;
case Type::STRING:
new (&data.stringValue) std::string(other.data.stringValue);
break;
case Type::ARRAY:
data.arrayValue = new std::vector<JsonValue>(*other.data.arrayValue);
break;
default:
break;
}
}
return *this;
}

void clear() {
switch (type) {
case Type::STRING:
data.stringValue.~basic_string();
break;
case Type::ARRAY:
delete data.arrayValue;
break;
default:
break;
}
type = Type::NULL_VALUE;
}

void print() const {
switch (type) {
case Type::NULL_VALUE:
std::cout << "null";
break;
case Type::BOOLEAN:
std::cout << (data.boolValue ? "true" : "false");
break;
case Type::NUMBER:
std::cout << data.numberValue;
break;
case Type::STRING:
std::cout << "\"" << data.stringValue << "\"";
break;
case Type::ARRAY:
std::cout << "[";
if (!data.arrayValue->empty()) {
data.arrayValue->at(0).print();
for (size_t i = 1; i < data.arrayValue->size(); ++i) {
std::cout << ", ";
data.arrayValue->at(i).print();
}
}
std::cout << "]";
break;
default:
std::cout << "{}"; // 简化表示对象
break;
}
}
};

int main() {
JsonValue nullValue;
JsonValue boolValue(true);
JsonValue numValue(42.5);
JsonValue strValue(std::string("测试字符串"));

std::cout << "Null: ";
nullValue.print();
std::cout << std::endl;

std::cout << "Boolean: ";
boolValue.print();
std::cout << std::endl;

std::cout << "Number: ";
numValue.print();
std::cout << std::endl;

std::cout << "String: ";
strValue.print();
std::cout << std::endl;

return 0;
}

输出:

Null: null
Boolean: true
Number: 42.5
String: "测试字符串"

实现自定义Optional类型

以下是使用非受限联合实现类似std::optional(C++17引入)功能的简化版本:

cpp
#include <iostream>
#include <string>

template <typename T>
class Optional {
private:
union {
T value;
char dummy; // 当没有值时使用的占位符
};
bool hasValue;

public:
Optional() : dummy(0), hasValue(false) {}

Optional(const T& v) : hasValue(true) {
new (&value) T(v);
}

Optional(const Optional& other) : hasValue(false) {
if (other.hasValue) {
new (&value) T(other.value);
hasValue = true;
}
}

Optional& operator=(const Optional& other) {
if (this != &other) {
if (hasValue) {
value.~T();
}

hasValue = other.hasValue;
if (hasValue) {
new (&value) T(other.value);
}
}
return *this;
}

~Optional() {
if (hasValue) {
value.~T();
}
}

bool has_value() const {
return hasValue;
}

T& get_value() {
if (!hasValue) {
throw std::runtime_error("Accessing value of empty Optional");
}
return value;
}

const T& get_value() const {
if (!hasValue) {
throw std::runtime_error("Accessing value of empty Optional");
}
return value;
}

void reset() {
if (hasValue) {
value.~T();
hasValue = false;
}
}
};

// 使用示例
int main() {
Optional<std::string> emptyOpt;
Optional<std::string> nameOpt("C++11");

if (emptyOpt.has_value()) {
std::cout << "Empty has value: " << emptyOpt.get_value() << std::endl;
} else {
std::cout << "Empty has no value" << std::endl;
}

if (nameOpt.has_value()) {
std::cout << "Name has value: " << nameOpt.get_value() << std::endl;
}

// 重置
nameOpt.reset();

if (!nameOpt.has_value()) {
std::cout << "Name now has no value" << std::endl;
}

return 0;
}

输出:

Empty has no value
Name has value: C++11
Name now has no value

注意事项和最佳实践

使用非受限联合体时,请牢记以下几点:

  1. 管理活跃成员:始终记录当前哪个成员是"活跃的",避免访问未初始化的成员。

  2. 手动调用构造和析构函数:对于非平凡类型的成员(如含有构造/析构函数的类),必须手动构造和析构。

  3. 考虑使用std::variant:如果你使用的是C++17或更高版本,考虑直接使用标准库的std::variant,它提供了类型安全且更易用的API。

  4. 注意对齐:不同类型可能有不同的对齐需求,这可能导致联合体的大小大于其最大成员的大小。

  5. 小心处理异常:在切换活跃成员时,要确保正确处理可能发生的异常,避免内存泄漏或未定义行为。

总结

C++11的非受限联合体是一个强大的特性,它打破了传统联合体只能包含POD类型的限制,使我们可以在联合体中使用具有复杂语义的类型。这为内存优化和变体类型的实现提供了更多可能性。

非受限联合体的主要应用场景包括:

  • 实现变体类型(如自定义variant或optional)
  • 优化内存使用
  • 实现状态机或解析器
  • 数据序列化/反序列化

虽然使用非受限联合体需要更多的手动管理工作(构造/析构/跟踪活跃成员),但在某些场景下,这是一个非常有价值的工具。

练习

  1. 使用非受限联合体实现一个可以存储int、double、bool和std::string的变体类型,提供类型安全的访问方法。

  2. 扩展上面的JsonValue示例,添加对象(map/dictionary)类型的支持。

  3. 修改Optional类实现,添加提供默认值的get_value_or方法。

  4. 思考:比较std::variant、std::any和使用非受限联合体自定义变体类型的优缺点。

进一步阅读

  • C++11标准文档中关于非受限联合体的章节
  • 《Effective Modern C++》由Scott Meyers撰写,了解更多C++11/14最佳实践
  • C++17的std::variant和std::optional标准库组件文档