C++那些事之玩转optional

2023-09-02 10:39:12 浏览数 (1)

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 {
}

仔细想想这样子写有什么问题呢?

  1. 内存对齐开销,增加了bool padding的开销。
  2. 构造T对象的开销,例如:T无效时,是没有必要的。

那么如何优化呢?

3.指针版optional

代码语言:javascript复制
template <typename T>
class Optional {
private:
  std::unique_ptr<T> value;
};

调用处:

代码语言:javascript复制
if (val) {
   // has value
} else {
   // nullptr
}

这个虽然解决了上述的问题,却引发了下面几个问题:

  1. 内存开销:std::unique_ptr使用了堆内存来存储实际值。这意味着每个可选类型对象都需要额外的堆内存分配,这可能会导致内存开销增加。
  2. 不能存储空值: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的实现,欢迎大家转发支持。

0 人点赞