【问题标题】:Unit test to check for `noexcept` property for a C++ method用于检查 C++ 方法的“noexcept”属性的单元测试
【发布时间】:2019-10-23 21:27:51
【问题描述】:

我有几种方法

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

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

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

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

【问题讨论】:

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


    【解决方案1】:

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

    【讨论】:

    • 非常感谢!感觉有点尴尬,因为我什至可以从cppreference.com 得到它。你打开了我的眼睛。
    • 请注意,如果S 不是默认可构造的,您也可以使用noexcept(declval<S>().foo()) 代替noexcept(S().foo())
    • std::declval 方法还有另一个好处:它允许您将static_assert 放在 类中,例如在函数声明之后,就像S 不需要' t 是完整的,std::declval<S>() 才有效(noexcept 的操作数 expr 将不被计算)。即,您可以将 static_assert(noexcept(std::declval<S>().foo())) 放在类的定义中,例如,紧接在 foo() 的声明(/定义)下方。
    • ... 特别是,从 [declval]/3: declval 的模板参数 T 可能是不完整的类型。”.
    【解决方案2】:

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

    要检查参数的类型和返回类型是否相同,可以使用简单的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&lt;S&gt;().foo(std::declval&lt;arg_1_t&gt;(), std::declval&lt;arg_2_t&gt;()))

    // 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,因为你很少在野外看到可变参数函数和值类别限定的成员函数)

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

    【讨论】:

      猜你喜欢
      • 2014-04-07
      • 1970-01-01
      • 1970-01-01
      • 2011-11-14
      • 2017-07-01
      • 2011-06-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多