在C ++ 11中,重载std::unique_lock
constructor以接受类型标记defer_lock_t
,try_to_lock_t
和adopt_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 );
这些是空类(类型标签)defined as follows:
struct defer_lock_t { };
struct try_to_lock_t { };
struct adopt_lock_t { };
这允许用户通过传递这些类中的pre-defined instances之一来消除三个构造函数之间的歧义:
constexpr std::defer_lock_t defer_lock {};
constexpr std::try_to_lock_t try_to_lock {};
constexpr std::adopt_lock_t adopt_lock {};
我很惊讶这不是作为enum
实现的。据我所知,使用enum
会:
为什么标准库使用类型标记而不是enum
来消除这些构造函数的歧义?也许更重要的是,在写这种情况时我是否也更喜欢使用类型标记我自己的C ++代码?
答案 0 :(得分:11)
这是一种称为标签调度的技术。它允许在给定客户端所需的行为的情况下调用适当的构造函数。
标记的原因是用于标记的类型因此不相关,并且在重载解析期间不会发生冲突。类型(而不是枚举的值)用于解析重载的函数。此外,标签可用于解决原本不明确的呼叫;在这种情况下,标签通常基于某些类型的特征。
使用模板进行标签调度意味着只需要实现给定构造所需的代码。
标签调度允许更容易阅读代码(至少在我看来)和更简单的库代码;构造函数没有switch
语句,并且在执行构造函数本身之前,可以基于这些参数在初始化列表中建立不变量。当然,你的milage可能会有所不同,但这是我使用标签的一般经验。
Boost.org有关于标签调度技术的文章。它的使用历史似乎可以追溯到at least as far as the SGI STL。
为什么标准库使用类型标记而不是枚举来消除这些构造函数的歧义?
在重载解析和可能的实现过程中,类型将比枚举更强大和灵活;请记住,枚举最初是无遮盖的,并且限制了它们的使用方式(与标签形成对比)。
标签的其他值得注意的原因;
shared_lock
和lock_guard
也会使用这些标记,但在lock_guard
的情况下,只使用adopt_lock
。枚举会引入更多潜在的错误条件。 我认为优先权和历史也在这里起作用。鉴于标准库和其他地方广泛使用;它不太可能改变库中实现的情况,例如原始示例。
也许更重要的是,在编写自己的C ++代码时,我是否还希望在这种情况下使用类型标记?
这基本上是一个设计决定。两者都可以而且应该用于解决他们解决的问题。我已经使用标签来"路由"数据和类型到正确的功能;特别是当实现在编译时不兼容并且存在任何重载决策时。
标准库std::advance
通常作为如何使用标签调度来实现和优化基于所用类型的特征(或特征)的算法的示例(在这种情况下,当迭代器是随机的时)访问迭代器)。
如果使用得当,它是一种强大的技术,不应忽视。如果使用枚举,请使用旧的未作用域的枚举枚举。
答案 1 :(得分:0)
使用这些标记可以利用该语言的类型系统。这与模板元编程密切相关。简单来说,使用这些标记允许调度决定在编译时静态地调用哪个构造函数。这为编译器优化留出了空间,提高了运行时效率,并使std::unique_lock
的模板元编程更容易。这是可能的,因为标签具有不同的静态类型。使用enum
时,无法完成此操作,因为在编译时无法预见enum
的值。请注意,使用标记进行区分是一种常见的模板元编程技术。只需查看标准库使用的iterator tags。
答案 2 :(得分:-1)
关键是,如果您想使用enum
添加其他功能,则应编辑enum
,然后重建所有使用您的功能和enum
的项目。此外,还有一个函数将枚举作为参数并使用switch
或其他东西。这会将多余的代码带入您的应用程序。
否则,如果您将重载函数与标记一起使用,则可以轻松添加另一个标记并添加另一个重载函数,而不会触及旧标记。这更具反向兼容性。
答案 3 :(得分:-1)
我怀疑这是优化。请注意,在编译时选择使用类型(原样)正确的版本。正如您所指出的,在运行时使用enum
(可能)在某些条件语句(可能是switch
)中被选中。
在许多实现中,锁的获取和释放频率非常高,设计人员可能会考虑分支预测和隐含的内存同步事件,这可能是一个重要问题。
我的论证中的缺陷(你也指出)是构造函数可能是内联的,并且很可能无论如何都会优化条件。
请注意,使用'dummy'参数是与实际提供命名构造函数最接近的模拟。
答案 4 :(得分:-1)
此方法称为标签调度(我可能错了)。具有不同值的枚举类型在编译时只是一种类型,枚举值不能用于重载构造函数。因此,对于枚举,它将是一个带有switch语句的构造函数。标签调度等同于编译时的switch语句。每个标记类型都指定:此构造函数将执行的操作,它将如何尝试获取锁定。当您想在编译时做出决定并使用枚举在运行时做出决定时,您应该使用类型标记。
答案 5 :(得分:-1)
因为,在std::unique_lock<Mutex>
中,您不希望强制 Mutex
拥有lock
或try_lock
方法可能永远不需要被召唤。
如果它接受了enum
参数,那么这两种方法都需要存在。