为什么std :: unique_lock使用类型标记来区分构造函数?

Mic*_*val 18 c++ c++11

在C++ 11中,std::unique_lock构造被重载以接受型标签defer_lock_t,try_to_lock_tadopt_lock_t:

unique_lock( mutex_type& m, std::defer_lock_t t );
unique_lock( mutex_type& m, std::try_to_lock_t t );
unique_lock( mutex_type& m, std::adopt_lock_t t );
Run Code Online (Sandbox Code Playgroud)

这些是空类(类型标记),定义如下:

struct defer_lock_t { };
struct try_to_lock_t { };
struct adopt_lock_t { };
Run Code Online (Sandbox Code Playgroud)

这允许用户通过传递这些类的预定义实例之一来消除三个构造函数之间的歧义:

constexpr std::defer_lock_t defer_lock {};
constexpr std::try_to_lock_t try_to_lock {};
constexpr std::adopt_lock_t adopt_lock {};
Run Code Online (Sandbox Code Playgroud)

我很惊讶这不是作为一个实现的enum.据我所知,使用一个enum会:

  • 更容易实现
  • 不要改变语法
  • 允许在运行时更改参数(虽然在这种情况下不是很有用).
  • (可能)可以由编译器内联,没有性能损失

为什么标准库使用类型标签而不是a enum来消除这些构造函数的歧义?也许更重要的是,在编写自己的C++代码时,我是否还喜欢在这种情况下使用类型标签?

Nia*_*all 11

标签调度

这是一种称为标签调度技术.它允许在给定客户端所需的行为的情况下调用适当的构造函数.

标记的原因是用于标记的类型因此是不相关的,并且在重载解析期间不会发生冲突.类型(而不是枚举的值)用于解析重载的函数.此外,标签可用于解决原本不明确的呼叫; 在这种情况下,标签通常基于某些类型特征.

使用模板进行标签分派意味着只需要实现给定构造所需的代码.

标签调度允许更容易阅读代码(至少在我看来)和更简单的库代码; switch在执行构造函数本身之前,构造函数将没有语句,并且可以在初始化列表中基于这些参数建立不变量.当然,你的milage可能会有所不同,但这是我使用标签的一般经验.

Boost.org撰写了关于标签调度技术的文章.它的使用历史似乎至少可以追溯到SGI STL.

为什么要用它?

为什么标准库使用类型标记而不是枚举来消除这些构造函数的歧义?

在重载解析期间使用的类型将更强大和灵活,并且可能的实现比枚举更强大; 请记住,枚举最初是无遮盖的,并且限制了它们的使用方式(与标签形成对比).

标签的其他值得注意的原因;

  • 编译时可以决定使用哪个构造函数,而不是运行时.
  • 不允许更多"hacky"代码,其中整数被强制转换为枚举类型,其值不是为了满足 - 需要设计决策来处理这个问题,然后实现代码以满足任何结果异常或错误.
  • 请记住,shared_lock并且lock_guard也使用这些标签,但在使用的情况下lock_guard,仅使用这些标签adopt_lock.枚举会引入更多潜在的错误条件.

我认为优先权和历史也在这里起作用.鉴于标准库和其他地方的广泛使用; 它不太可能改变库中实现的情况,例如原始示例.

也许更重要的是,在编写自己的C++代码时,我是否还喜欢在这种情况下使用类型标签?

这基本上是一个设计决定.两者都可以而且应该用于解决他们解决的问题.我使用标签将数据和类型"路由"到正确的函数; 特别是当实现在编译时不兼容并且存在任何重载决策时.

标准库std::advance通常作为如何使用标签分派来实现和优化基于所用类型的特征(或特征)的算法的示例(在这种情况下,当迭代器是随机访问迭代器时).

如果使用得当,它是一种强大的技术,不应忽视.如果使用枚举,则支持较旧的未作用域的枚举枚举.

  • @MichaelKoval*"我想它可能是以这种方式实现的,只是为了与标准库的其他部分保持一致."*例如原子操作的内存顺序?([使用枚举](http://en.cppreference.com/w/cpp/atomic/memory_order));) (2认同)

Hea*_*avy -1

要点是,如果您想使用 来添加另一个函数enum,您应该编辑您的enum,然后重建所有使用您的函数 和 的项目enum。此外,还会有一个函数以 enum 作为参数并使用switch或其他东西。这会将多余的代码带入您的应用程序中。

否则,如果您使用带有标签的重载函数,您可以轻松添加另一个标签并添加另一个重载函数,而无需触及旧的函数。这更加向后兼容。