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

时间:2019-06-08 21:05:01

标签: c++ unit-testing cmake c++17 noexcept

我有几种方法

  • 必须被标记为noexcept
  • 不得标记为noexcept

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

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

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

2 个答案:

答案 0 :(得分:5)

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())

答案 1 :(得分:2)

由于您使用的是C ++ 17,因此函数的noexcept性质是其类型的一部分。

要检查参数的类型并返回相同类型的类型,只需使用简单的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就可以逃脱,因为您很少在野外看到可变参数函数和值类别合格的成员函数)< / p>

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