C++那些事之玩转optional
C++那些事之玩转optional
0.导语
1.引入
2.简易版optional
3.指针版optional
4.pair版optional
5.storage版optional
6.union版optional
7.小项目:字符串转数字
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变量来标记当前类模版是否办函值。
template <typename T>
class Optional {
private:bool hasValue;T value;
};
在使用的地方:
if (optionalInt.hasValue()) {
} else {
}
仔细想想这样子写有什么问题呢?
内存对齐开销,增加了bool+padding的开销。
构造T对象的开销,例如:T无效时,是没有必要的。
那么如何优化呢?
3.指针版optional
template <typename T>
class Optional {
private:std::unique_ptr<T> value;
};
调用处:
if (val) {// has value
} else {// nullptr
}
这个虽然解决了上述的问题,却引发了下面几个问题:
内存开销:
std::unique_ptr
使用了堆内存来存储实际值。这意味着每个可选类型对象都需要额外的堆内存分配,这可能会导致内存开销增加。不能存储空值:
std::unique_ptr
要求始终持有一个有效的指针,因此无法表示空值。如果你需要表示一个可选类型的空值状态,你可能需要引入其他的标志来表示空值状态。
对于第二点,给个示例,当直接获取数据是,此时应该预期返回空值,而不是nullptr。
// 报错:空指针
std::cout << "Value: " << optionalInt.getData() << std::endl;
4.pair版optional
对于前面几个版本都有点问题,继续优化,我们不用unique_ptr,回到第一个版本,干掉bool变量不就解决了一开始的内存对齐问题吗。
template <typename T>
class Optional {
private:T data;
};
在返回数据的时候带上bool标记不就行了。
std::pair<bool, T> getData() const {if (con) {return std::make_pair(true, data);} else {return std::make_pair(false, data);}
}
调用的时候我们通过first、second这种方式语义太不明确了,接口不够清晰。
otherOptionalInt.getData().first
otherOptionalInt.getData().second
5.storage版optional
我们希望可选对象默认情况下不构造包含的对象。实现它的一种方法是使用std::aligned_storage为所包含的对象保留空间,随后用placement new,即使用new运算符在现有位置构造一个对象。
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中实现为:
struct _Empty_byte { };
union _Storage
{_Empty_byte _M_empty;_Up _M_value;
};
使用处:
_Storage<_Stored_type> _M_payload;
我们对这个做一层抽象,变为了当前的union版本实现:
struct _Empty_byte { };template <typename T>
class Optional {
private:union {T value;_Empty_byte empty;};bool hasValue;
}
这下跟stl实现差不多了,缺了一些继承/封装等等。
7.小项目:字符串转数字
给大家布置一个课堂练习,壳子为,请补充完代码。
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;elsecout << num << " is not an integer" << endl;num = "42z";if (auto n = stonum(num); n.has_value())cout << num << " is integer " << *n << endl;elsecout << num << " is not an integer" << endl;
}
本节抽丝剥茧,一层层的揭开optional的实现,欢迎大家转发支持。