C++14中实现可空类型安全递归解引用的方案问询
安全递归解引用可空类型(C++14)
问题背景
我们常遇到被std::optional、std::shared_ptr这类可空类型多层嵌套包装的数据结构,示例如下:
#include <iostream> #include <memory> #include <optional> struct Entry { std::optional<std::string> name; }; struct Container { std::optional<std::shared_ptr<Entry>> entry; }; int main() { Entry entry{"name"}; Container container{std::make_shared<Entry>(entry)}; // ... return 0; }
要读取Container中Entry的name字段,直接写*((*container.entry)->name)不仅可读性差,还会因可空类型未初始化直接崩溃;而层层嵌套的if判断又过于繁琐:
if (container.entry) { const auto ptr = *container.entry; if (ptr != nullptr) { const auto opt = ptr->name; if (opt) { const std::string name = *opt; std::cout << name << std::endl; } } }
我们期望一种更简洁的写法,比如:
const auto entry = recursive_dereference(container.entry); const auto name = recursive_dereference(entry.name); std::cout << name.value_or("empty") << std::endl;
但现有递归解引用实现会在可空类型未初始化时崩溃,需要修改它,让它在路径中存在未设置字段时返回空的std::optional,否则返回对应值。
已有尝试
方案1:分开检查与解引用
先实现is_safe函数递归检查路径有效性,再调用原递归解引用:
template<typename T> bool is_safe(T&& /*t*/, std::false_type /*can_deref*/) { return true; } // Forward declaration template<typename T> bool is_safe(T&& t); template<typename T> bool is_safe(T&& t, std::true_type /*can_deref*/) { if (t) { return is_safe(*std::forward<T>(t)); } return false; } template<typename T> bool is_safe(T&& t) { return is_safe(std::forward<T>(t), can_dereference<T>{}); }
使用方式:
if (is_safe(container.entry)) { const auto entry = recursive_dereference(container.entry); // 使用entry }
该方案需要分开检查和解引用,不够优雅。
方案2:显式指定返回类型的递归解引用
实现了返回std::optional的版本,但需显式指定最终类型:
template <typename FT, typename T> auto deref(T&& t, std::false_type) -> std::optional<FT> { return std::forward<T>(t); } template <typename FT, typename T> auto deref(T&& t) -> std::optional<FT>; template <typename FT, typename T> auto deref(T&& t, std::true_type) -> std::optional<FT> { if (t) { return deref<FT>(*std::forward<T>(t)); } return std::nullopt; } template <typename FT, typename T> auto deref(T&& t) -> std::optional<FT> { return deref<FT>(std::forward<T>(t), can_dereference<T>{}); }
使用示例:
std::cout << deref<Entry>(container.entry).has_value() << std::endl; std::cout << deref<Entry>(emptyContainer.entry).has_value() << std::endl;
输出:
1 0
但显式指定类型不够灵活,希望能自动推导最终类型。
优化解决方案
借助C++14特性,我们可以实现无需显式指定类型、自动递归解引用并返回std::optional的工具函数。首先定义辅助特性can_dereference判断类型是否支持解引用:
#include <type_traits> #include <utility> // 判断类型是否可解引用(支持*或->操作) template<typename T> struct can_dereference_impl { template<typename U> static auto test(int) -> decltype(std::declval<U>().operator->(), std::true_type{}); template<typename U> static auto test(long) -> decltype(*std::declval<U>(), std::true_type{}); template<typename U> static auto test(...) -> std::false_type; using type = decltype(test<T>(0)); }; template<typename T> using can_dereference = typename can_dereference_impl<T>::type;
然后实现核心的safe_deref函数:
// 终止条件:不可解引用的类型,包装为std::optional返回 template<typename T> auto safe_deref(T&& t, std::false_type) -> std::optional<typename std::decay<T>::type> { return std::forward<T>(t); } // 递归条件:可解引用且非空时,继续递归解引用 template<typename T> auto safe_deref(T&& t, std::true_type) -> decltype(safe_deref(*std::forward<T>(t))) { if (t) { return safe_deref(*std::forward<T>(t)); } return {}; // 返回空的std::optional } // 对外接口 template<typename T> auto safe_deref(T&& t) -> decltype(safe_deref(std::forward<T>(t), can_dereference<T>{})) { return safe_deref(std::forward<T>(t), can_dereference<T>{}); }
现在可以优雅地使用:
int main() { Entry entry{"name"}; Container container{std::make_shared<Entry>(entry)}; Container emptyContainer; // 获取Entry对象的optional auto opt_entry = safe_deref(container.entry); if (opt_entry) { // 获取name的optional auto opt_name = safe_deref(opt_entry->name); std::cout << opt_name.value_or("empty") << std::endl; // 输出name } // 直接链式调用 auto opt_name_direct = safe_deref(safe_deref(container.entry)->name); std::cout << opt_name_direct.value_or("empty") << std::endl; // 空容器的情况 auto opt_empty_entry = safe_deref(emptyContainer.entry); std::cout << opt_empty_entry.has_value() << std::endl; // 输出0 return 0; }
该实现优势:
- 无需显式指定最终类型,自动推导
- 一次调用完成检查与解引用,返回
std::optional - 支持
std::optional、std::shared_ptr、std::unique_ptr等所有支持operator bool()判断非空且可解引用的类型
内容的提问来源于stack exchange,提问作者Wilhem Meignan
相关产品推荐
相关产品推荐

