在C ++中使用枚举而不是struct进行标记分派

时间:2018-08-16 16:08:15

标签: c++ c++17 tag-dispatching

让我们从标准库中实现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);

后者更为简洁。

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

2 个答案:

答案 0 :(得分:11)

首先,您不希望标记类型是{}可构造的,而是要明确命名它们。由于unique_lock含糊不清,因此这不适用于unique_lock<std::mutex> lk(m, {}),但是有一个通用原则。标记类型的设计使得您必须编写std::defer_lock(或者,如果您确实需要,  std::defer_lock_t()

第二,您实际上只想在打算使用它们的特定上下文中使用标记类型。如果您将它们设置为enum,则将引入所有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实现视为胜利。标准库中没有那个标记类型。

答案 1 :(得分:3)

除了 Barry 列出的原因之外的另一个(次要)好处是,如果函数调用没有内联,则枚举标记具有需要传递到函数的状态,而结构标记则没有。即使看起来枚举是空的,无作用域的枚举总是至少有一个字节的状态可以转换成它们,而作用域的枚举总是至少有一位状态。见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