用于检查 C++ 方法的 `noexcept` 属性 的单元测试

Unit test to check for `noexcept` property for a C++ method

我有几种方法

如何编写单元测试来正确检查标记 noexcept 的方法?

原因:确保将来这些属性不会在其他开发人员或我的邪恶版本的重构过程中被更改。

目前,我使用 CMake/CTest 并将手写的可执行文件添加到测试套件中。

noexcept 也是一个运算符。您可以在静态断言中使用它:

void foo() noexcept { }
void bar() { }

static_assert(noexcept(foo())); // OK
static_assert(noexcept(bar())); // Will fail

如果是成员函数,则:

struct S {
    void foo() noexcept { }
};

static_assert(noexcept(S().foo()));

没有函数调用或任何执行。 noexcept 运算符仅检查表达式,实际上并不对其求值。

要要求函数不能是noexcept,只需使用!noexcept(bar())

由于您使用的是 C++17,函数的 noexcept-ness 是其类型的一部分。

要检查参数的类型和 return 类型是否相同,您可以使用简单的 std::is_same:

void foo(int, int) noexcept;
void bar(long, float);
struct S {
    void foo(int, int) noexcept;
    void bar(long, float);
};

static_assert(std::is_same_v<decltype(foo), void(int, int) noexcept>);
static_assert(std::is_same_v<decltype(bar), void(long, float)>);
static_assert(std::is_same_v<decltype(&S::foo), void(S::*)(int, int) noexcept>);
static_assert(std::is_same_v<decltype(&S::bar), void(S::*)(long, float)>);

您还可以使用模板参数推导来查看函数类型是否为 noexcept,而无需检查 noexcept(std::declval<S>().foo(std::declval<arg_1_t>(), std::declval<arg_2_t>())):

// Arguments, return type and type of the class that has the member function are
// all deduced, but this overload is only called if noexcept
template<class RetT, class T, class... Args>
constexpr bool is_noexcept_function(RetT(T::*)(Args...) noexcept) {
    return true;
}
// And this one is called if not noexcept
template<class RetT, class T, class... Args>
constexpr bool is_noexcept_function(RetT(T::*)(Args...)) {
    return false;
}

static_assert(is_noexcept_function(&S::foo));
static_assert(!is_noexcept_function(&S::bar));

对于const(和其他)合格的成员函数以及具有可变参数的函数,完整的解决方案有点长:

#include <type_traits>

// Check if a regular function is noexcept
template<class Ret, class... Args>
constexpr std::false_type is_noexcept_function(Ret(Args...)) noexcept {
    return {};
}

template<class Ret, class... Args>
constexpr std::true_type is_noexcept_function(Ret(Args...) noexcept) noexcept {
    return {};
}

// Check if a regular function with C-style variadic arguments is noexcept
template<class Ret, class... Args, bool is_noexcept>
constexpr std::false_type is_noexcept_function(Ret(Args......)) noexcept {
    return {};
}

template<class Ret, class... Args, bool is_noexcept>
constexpr std::true_type is_noexcept_function(Ret(Args......) noexcept) noexcept {
    return {};
}

// Check if a member function is noexcept
#define DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD(QUALIFIER) \
template<class Ret, class T, class... Args> \
constexpr std::false_type is_noexcept_function(Ret(T::*)(Args...) QUALIFIER) noexcept { \
    return {}; \
} \
template<class Ret, class T, class... Args> \
constexpr std::true_type is_noexcept_function(Ret(T::*)(Args...) QUALIFIER noexcept) noexcept { \
    return {}; \
} \
template<class Ret, class T, class... Args, bool is_noexcept> \
constexpr std::false_type is_noexcept_function(Ret(T::*)(Args......) QUALIFIER) noexcept { \
    return {}; \
} \
template<class Ret, class T, class... Args, bool is_noexcept> \
constexpr std::true_type is_noexcept_function(Ret(T::*)(Args......) QUALIFIER noexcept) noexcept { \
    return {}; \
}

#define DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD_VALUE_CLASS(VALUE_CLASS) \
    DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD(VALUE_CLASS) \
    DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD(const VALUE_CLASS) \
    DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD(volatile VALUE_CLASS) \
    DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD(const volatile VALUE_CLASS)

DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD_VALUE_CLASS()
DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD_VALUE_CLASS(&)
DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD_VALUE_CLASS(&&)

#undef DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD
#undef DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD_VALUE_CLASS


// Usage example

void foo(int, int) noexcept;
void bar(long, float);
struct S {
    void foo(int, int) const noexcept;
    void bar(long, float) &&;
};

static_assert(is_noexcept_function(foo));
static_assert(!is_noexcept_function(bar));
static_assert(is_noexcept_function(&S::foo));
static_assert(!is_noexcept_function(&S::bar));

(大多数情况下,您可以只支持 RetT(Args...)RetT(T::*)(Args...)RetT(T::*)(Args...) const,因为您很少在野外看到可变参数函数和值类别限定的成员函数)

这不适用于模板化或重载的 functions/member 函数。这是因为 noexcept-ness 可能取决于模板参数或重载参数类型。您可以手动提供模板参数(例如 is_noexcept(add<int>)!is_noexcept(add<std::string>))或退回到 noexcept 运算符和 std::declval.