12

让我们std::unique_lock从标准库中实现:

struct defer_lock_t { explicit defer_lock_t() = default; };
struct try_to_lock_t { explicit try_to_lock_t() = default; };
struct adopt_lock_t { explicit adopt_lock_t() = default; };

inline constexpr defer_lock_t  defer_lock {};
inline constexpr try_to_lock_t try_to_lock {};
inline constexpr adopt_lock_t  adopt_lock {};

unique_lock (mutex_type& m, defer_lock_t t) noexcept;
unique_lock (mutex_type& m, try_to_lock_t t);
unique_lock (mutex_type& m, adopt_lock_t t);

有没有理由不/不能/不应该使用枚举而不是结构来实现标签调度?如:

enum defer_lock_t { defer_lock };
enum try_to_lock_t { try_to_lock };
enum adopt_lock_t { adopt_lock };

unique_lock (mutex_type& m, defer_lock_t t) noexcept;
unique_lock (mutex_type& m, try_to_lock_t t);
unique_lock (mutex_type& m, adopt_lock_t t);

后者更简洁。

我能想到的使用结构的唯一优点是继承(例如,迭代器标签)。但在所有其他情况下,为什么不使用枚举呢?

4

2 回答 2

14

首先,您不希望标签类型是可{}构造的,您希望明确命名它们。这并不特别适用,unique_lock因为unique_lock<std::mutex> lk(m, {})会模棱两可,但有一个一般原则。标签类型的设计使得您必须编写std::defer_lock(或者,如果您真的想要, std::defer_lock_t())。

其次,您真的只想在要使用它们的特定上下文中使用标记类型。如果您将它们设为enums,那么您将引入所有enum功能 - 例如可转换为整数:

std::make_unique<int>(std::defer_lock); // ok?

// is this like super deferred?
auto x = std::defer_lock * 2;

// what do you get when you multiply six by nine?
std::unique_lock lk(m, static_cast<std::defer_lock_t>(42));

这些其他表达式没有意义,所以最好不要它们存在。

第三,在只有少量固定字符的情况下实现标准库的简洁性并不是一个大问题。所以我什至不认为enum实施是一场胜利。标准库中的标签类型并不多。

于 2018-08-16T16:19:14.613 回答
3

除了 Barry 列出的原因之外,另一个(次要)好处是,如果函数调用未内联,则 enum 标记具有需要传递给函数的状态,而 struct 标记则没有。即使看起来枚举是空的,无作用域的枚举总是至少有一个字节的状态可以被转换到它们中,而作用域的枚举总是至少有一个状态位。见http://eel.is/c++draft/enum#dcl.enum-7

给定

struct s {};
enum e {};

void a(s);
void b(e);

void c() {
    a(s());
}

void d() {
    b(e());
}

64 位 Linux 的 clang 和 gcc 都生成

c():                                  # @c()
        jmp     a(s)                          # TAILCALL
d():                                  # @d()
        xor     edi, edi
        jmp     b(e)                          # TAILCALL

但请注意,在 Windows 上,调用约定似乎阻止了这一点(MSVC 代码生成):

$T1 = 8
void c(void) PROC                                      ; c, COMDAT
        movzx   ecx, BYTE PTR $T1[rsp]
        jmp     void a(s)                   ; a
void c(void) ENDP                                      ; c

void d(void) PROC                                      ; d, COMDAT
        xor     ecx, ecx
        jmp     void b(e)                            ; b
void d(void) ENDP                                      ; d

现场观看:https ://godbolt.org/z/ss7Ke64ca

于 2021-04-13T19:15:45.790 回答