C 那些事之玩转optional
0.导语
本节将会引入5个版本的optional实现,最终揭秘C STL optional实现,最后给出一个小项目作为练习的例子,让大家感受step by step学习的乐趣,所有代码、答案与相关参考资料,已更新于知识星球,欢迎大家加入学习。
1.引入
C 17之后,C 标准库提供了std::optional,它是一个管理可选包含值的类模板。可选类型或有时也称为Maybe类型表示可选值的封装。
The class template
std::optional
manages an optional contained value, i.e. a value that may or may not be present.
cppreference:
https://en.cppreference.com/w/cpp/utility/optional
那么在C 17之前自己实现的话,如何造一个std::optional?
2.简易版optional
第一个版本是比较简单的,我们引入bool变量来标记当前类模版是否办函值。
代码语言:javascript复制template <typename T>
class Optional {
private:
bool hasValue;
T value;
};
在使用的地方:
代码语言:javascript复制if (optionalInt.hasValue()) {
} else {
}
仔细想想这样子写有什么问题呢?
- 内存对齐开销,增加了bool padding的开销。
- 构造T对象的开销,例如:T无效时,是没有必要的。
那么如何优化呢?
3.指针版optional
代码语言:javascript复制template <typename T>
class Optional {
private:
std::unique_ptr<T> value;
};
调用处:
代码语言:javascript复制if (val) {
// has value
} else {
// nullptr
}
这个虽然解决了上述的问题,却引发了下面几个问题:
- 内存开销:
std::unique_ptr
使用了堆内存来存储实际值。这意味着每个可选类型对象都需要额外的堆内存分配,这可能会导致内存开销增加。 - 不能存储空值:
std::unique_ptr
要求始终持有一个有效的指针,因此无法表示空值。如果你需要表示一个可选类型的空值状态,你可能需要引入其他的标志来表示空值状态。
对于第二点,给个示例,当直接获取数据是,此时应该预期返回空值,而不是nullptr。
代码语言:javascript复制// 报错:空指针
std::cout << "Value: " << optionalInt.getData() << std::endl;
4.pair版optional
对于前面几个版本都有点问题,继续优化,我们不用unique_ptr,回到第一个版本,干掉bool变量不就解决了一开始的内存对齐问题吗。
代码语言:javascript复制template <typename T>
class Optional {
private:
T data;
};
在返回数据的时候带上bool标记不就行了。
代码语言:javascript复制std::pair<bool, T> getData() const {
if (con) {
return std::make_pair(true, data);
} else {
return std::make_pair(false, data);
}
}
调用的时候我们通过first、second这种方式语义太不明确了,接口不够清晰。
代码语言:javascript复制otherOptionalInt.getData().first
otherOptionalInt.getData().second
5.storage版optional
我们希望可选对象默认情况下不构造包含的对象。实现它的一种方法是使用std::aligned_storage为所包含的对象保留空间,随后用placement new,即使用new运算符在现有位置构造一个对象。
代码语言:javascript复制template <typename T>
class Optional {
private:
using StorageType =
typename std::aligned_storage<sizeof(T), alignof(T)>::type;
StorageType data;
bool hasValue;
};
6.union版optional
在C stl中实现为:
代码语言:javascript复制struct _Empty_byte { };
union _Storage
{
_Empty_byte _M_empty;
_Up _M_value;
};
使用处:
代码语言:javascript复制_Storage<_Stored_type> _M_payload;
我们对这个做一层抽象,变为了当前的union版本实现:
代码语言:javascript复制struct _Empty_byte { };
template <typename T>
class Optional {
private:
union {
T value;
_Empty_byte empty;
};
bool hasValue;
}
这下跟stl实现差不多了,缺了一些继承/封装等等。
7.小项目:字符串转数字
给大家布置一个课堂练习,壳子为,请补充完代码。
代码语言:javascript复制inline std::string trim(const std::string& s)
{
}
template<typename T = int>
std::optional<T> stonum(const std::string& st)
{
}
// 调用
int main()
{
string num;
num = "n1";
if (auto n = stonum(num); n.has_value())
cout << num << " is integer " << *n << endl;
else
cout << num << " is not an integer" << endl;
num = "42z";
if (auto n = stonum(num); n.has_value())
cout << num << " is integer " << *n << endl;
else
cout << num << " is not an integer" << endl;
}
本节抽丝剥茧,一层层的揭开optional的实现,欢迎大家转发支持。