首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >可以优化的Nop类

可以优化的Nop类
EN

Code Review用户
提问于 2022-01-17 14:32:13
回答 1查看 121关注 0票数 2

我设计了一个用于调试构建的类,它在发布模式下不会产生任何开销。例如,usecase是:我有一个函数,我想要计算它被调用的频率。为此,我可以编写以下内容

代码语言:javascript
复制
function void f() {
    #ifndef NDEBUG
        static int i = 0;
        ++i;
        std::cout << "Counter: " << i << "\n";
    #endif
    // do something
}

但这会让我的源代码受到预处理指令的打击。因此,我有了一个类的想法,上面的例子变成了:

代码语言:javascript
复制
function void f() {
    static Nop_t<int> i = 0;
    std::cout << "Counter: "_nop << i << "\n"_nop;
    ++i;
    // do something
}

默认情况下,在Debug中,Nop_t< T >T相同,否则它是一种不执行任何操作并接受所有可能的代码构造的类型。

现在是真正的实现和测试文件(我在Google中编写了测试,并对它们进行了修改,以便在这里发布)。

  • 这个类使用一些实用程序(主要是从Stackoverflow获取的)。
代码语言:javascript
复制
///
/// Utilities
///

 /* T_NDEBUG
 * Macro which is always defined.
 * It has the value true if NDEBUG is defined, and false otherwise 
 */
 #ifndef NDEBUG
     #define T_NDEBUG true
 #else
     #define T_NDEBUG false
 #endif

/** Standard preprocessor string concatenation macro which expands also macro arguments
 * Example: T_JOIN( abc123, __LINE__ )
 * Yields: abc123116  // or something else if the line of this macro changes
 */
#define T_JOIN( x, y ) T_JOIN_AGAIN( x, y )
#define T_JOIN_AGAIN( x, y ) x ## y

/**
 * Stuff for macro with variable number of arguments
 * @Link stackoverflow.com/questions/11761703
 *
 * @Example: We want to have macros SUM(a), SUM(a,b), SUM(a,b,c).
 *
 *        ```
 *        #define SUM( ... )       OVERLOADED_MACRO( SUM, __VA_ARGS__ )
 *
 *        #define SUM1( a )        (a)
 *        #define SUM2( a, b )     (a)+(b)
 *        #define SUM3( a, b, c )  (a)+(b)+(c)
 *        ```
 * Note: Macros without arguments are not possible in a portable way
 */

#define OVERLOADED_MACRO( M, ... ) _OVR( M, _COUNT_ARGS(__VA_ARGS__) ) ( __VA_ARGS__ )
#define _OVR( macroName, number_of_args )   _OVR_EXPAND(macroName, number_of_args)
#define _OVR_EXPAND( macroName, number_of_args )    macroName##number_of_args

#define _COUNT_ARGS( ... )  _ARG_PATTERN_MATCH( __VA_ARGS__, 9,8,7,6,5,4,3,2,1 )
#define _ARG_PATTERN_MATCH( _1,_2,_3,_4,_5,_6,_7,_8,_9, N, ... )   N

/** Macro removing "unused" warning in Debug Mode
 * Macro does not work reliable
 *
 * @example
 * function f( int a ) {
 *     MAYBE_UNUSED( a );
 * }
 */
#ifndef MAYBE_UNUSED //macro for disabling "unused function warning"
    #define MAYBE_UNUSED(expr) do{ (void)(expr); } while (0)
#endif
  • Nop类
代码语言:javascript
复制
///
/// Nop class
///

#include <iosfwd>
#include <utility>

/**
 * # Nop.hpp
 *
 * ## General description
 * This is a class which is either
 * - a typedef for a type, or
 * - a type which takes everything and does nothing (i.e. every use of a variable of this type is optimized away by the compiler)
 * ```
 * Nop_t< T, true >  // is a typedef for T
 * Nop_t< T, false >  // is the do-nothing type
 * Nop_t< T >  // is a typedef in Debug Mode, and the do-nothing type in Release mode
 * ```
 * 
 * ## Usage example
 * static Nop_t< std::atomic<int> > counter;
 * void func() {
 *     counter++;  // count how often this function is called, but only in Debug mode
 *     std::cout << (Nop_t<std::string>)"counter: " << counter;  // text is only printed in Debug Mode
 *     // do something
 * }
 * 
 * ## User defined literals
 * By using the macro NOP_LITERAL a user defined literal can be generated.
 *     NOP_LITERAL( name, condition )
 *     NOP_LITERAL( name )  // name defaults to "nop"
 * If condition is true, then the literal will become the usual literal, otherwise it will become a Nop_t
 * Example:    
 *     NOP_LITERAL( nop )
 *     void f() {
 *         Nop_t< int > value;
 *         std::cout << "Value: "_nop << value;  // Does nothing in Release mode
 *     }
 * Notes:
 *     This macro shall only be used in cpp files, otherwise the global namespace may get polluted.* 
 * 
 * ## Adding supported functions
 *  If a function / member function / typedef is missing, there are several ways to add it to the class
 *  1) Add it inside the class using the macros NOP_MEMBER_FUNCTION, NOP_FRIEND_FUNCTION, NOP_UNARY, NOP_BINARY_OPERATOR, NOP_TYPEDEF
 *  2) Add it inside the class handwritten. Such functions should be made as generic as possible
 *  3) Add it outside of the class using the macros NOP_FUNCTION, NOP_LAMBDA
 */

namespace detail {

    template< typename T >
    struct Nop_impl {
        // char make_struct_to_size_zero[0];  // Adding this member would make this struct have size 0, but this is not Standards compliant
        
        // This function shall accept anything and do nothing.
        // Type checking is not necessary, since it is done when "this class is disabled" and the underlying type is used (i.e. in Debug mode).
        template< typename ... Types > constexpr Nop_impl( Types && ... ) noexcept {}
        
        // This macro adds a typedef
        #define NOP_TYPEDEF( name, type ) using name = type;
        NOP_TYPEDEF( value_type, T )
        #undef NOP_TYPEDEF
        
        // This macro defines a member function, taking any arguments
        #define NOP_MEMBER_FUNCTION( func ) template< typename ... Types > constexpr Nop_impl func( Types && ... ) const noexcept { return Nop_impl{}; }
        NOP_MEMBER_FUNCTION( operator() )
        NOP_MEMBER_FUNCTION( empty )
        NOP_MEMBER_FUNCTION( size )
        #undef NOP_MEMBER_FUNCTION
    
        // Defining a function here takes any arguments, one of which a Nop_t,
        // defining a function outside the class using the NOP_FUNCTION macro, generates only a function taking one argument, a Nop_t.
        #define NOP_FRIEND_FUNCTION( func ) template< typename ... Types > friend constexpr Nop_impl func( Types && ... ) noexcept { return Nop_impl{}; }
        NOP_FRIEND_FUNCTION( sin )
        NOP_FRIEND_FUNCTION( swap )
        #undef NOP_FRIEND_FUNCTION
        
        // This macro defines a unary operator
        #define NOP_UNARY( op ) constexpr Nop_impl op noexcept { return Nop_impl{}; }
        NOP_UNARY( operator+() )
        NOP_UNARY( operator-() )
        NOP_UNARY( operator++() )
        NOP_UNARY( operator++(int) )  // NOLINT(cert-dcl21-cpp)
        NOP_UNARY( operator--() )
        NOP_UNARY( operator--(int) )  // NOLINT(cert-dcl21-cpp)
        NOP_UNARY( operator!() )
        NOP_UNARY( operator~() )
        #undef NOP_UNARY
    
        // This macro defines a binary operator
        #define NOP_BINARY_OPERATOR( op ) template< typename L, typename R >  friend constexpr Nop_impl operator op ( L &&, R && ) noexcept { return Nop_impl{}; }
        NOP_BINARY_OPERATOR( + )
        NOP_BINARY_OPERATOR( - )
        NOP_BINARY_OPERATOR( * )
        NOP_BINARY_OPERATOR( / )
        NOP_BINARY_OPERATOR( % )
        NOP_BINARY_OPERATOR( += )
        NOP_BINARY_OPERATOR( -= )
        NOP_BINARY_OPERATOR( *= )
        NOP_BINARY_OPERATOR( /= )
        NOP_BINARY_OPERATOR( %= )
        
        NOP_BINARY_OPERATOR( < )
        NOP_BINARY_OPERATOR( <= )
        NOP_BINARY_OPERATOR( >= )
        NOP_BINARY_OPERATOR( > )
        NOP_BINARY_OPERATOR( != )
        NOP_BINARY_OPERATOR( == )
        
        NOP_BINARY_OPERATOR( && )
        NOP_BINARY_OPERATOR( || )
        NOP_BINARY_OPERATOR( & )
        NOP_BINARY_OPERATOR( | )
        NOP_BINARY_OPERATOR( ^ )

        NOP_BINARY_OPERATOR( &= )
        NOP_BINARY_OPERATOR( |= )
        NOP_BINARY_OPERATOR( ^= )
        #undef NOP_BINARY_OPERATOR          
    
        // Section for special functions
        template< typename U > constexpr Nop_impl operator[]( const U & ) noexcept { return Nop_impl{}; }
        friend constexpr std::ostream& operator<<( std::ostream & os, Nop_impl ) noexcept { return os; };
    
    };
    
    // Since partial specialization of typedefs/using-directions is not allowed,
    // we use a helper struct with a function returning the wanted type.
    // This function can be used in a using directive then.
    template< typename T, bool E = !T_NDEBUG > struct make_nop;

    template< typename T >
    struct make_nop< T, true > {
        template< typename ... Args > constexpr static T make( Args && ... args ) noexcept( noexcept(T( std::forward< Args >( args ) ... )) ) { 
            return T( std::forward< Args >( args ) ... ); 
        };
    };
    
    template< typename T >
    struct make_nop< T, false > {
        template< typename ... Args > constexpr static Nop_impl< T > make( Args && ... args ) noexcept { 
            return Nop_impl< T >( std::forward< Args >( args ) ... ); 
        };
    };
}

template< typename T, bool E = !T_NDEBUG > using Nop_t = decltype( detail::make_nop<T,E>::make() );


// This macro defines a function taking exactly one argument of type Nop_t< T, false >, where T is any type
#define NOP_FUNCTION( func ) template< typename T > static inline constexpr \
    ::detail::Nop_impl< T > func( const ::detail::Nop_impl<T> & ) noexcept { return ::detail::Nop_impl< T >{}; }

// This macro defines user defined literals
// Example:
#define NOP_LITERAL( ... )       OVERLOADED_MACRO( NOP_LITERAL, __VA_ARGS__ )
#define NOP_LITERAL1( name ) \
    NOP_LITERAL2( name, !T_NDEBUG )
#define NOP_LITERAL2( name, condition ) \
    namespace nop { namespace T_JOIN( line, __LINE__ ) { \
        inline constexpr Nop_t< unsigned long long int, condition > operator""_ ## name ( unsigned long long int lit ) { return Nop_t< unsigned long long int, condition >{ lit }; } \
        inline constexpr Nop_t< long double, condition > operator ""_ ## name( long double lit ) { return Nop_t< long double, condition >{ lit }; } \
        inline constexpr Nop_t< char, condition > operator ""_ ## name( char lit ) { return Nop_t< char, condition >{ lit }; } \
        inline constexpr Nop_t< wchar_t, condition > operator ""_ ## name( wchar_t lit ) { return Nop_t< wchar_t, condition >{ lit }; } \
        inline constexpr Nop_t< char16_t, condition > operator ""_ ## name( char16_t lit ) { return Nop_t< char16_t, condition >{ lit }; } \
        inline constexpr Nop_t< char32_t, condition > operator ""_ ## name( char32_t lit ) { return Nop_t< char32_t, condition >{ lit }; } \
        inline constexpr Nop_t< const char *, condition > operator ""_ ## name( const char * lit, size_t ) { return Nop_t< const char *, condition >{ lit }; } \
        inline constexpr Nop_t< const wchar_t *, condition > operator ""_ ## name( const wchar_t * lit, size_t ) { return Nop_t< const wchar_t *, condition >{ lit }; } \
        inline constexpr Nop_t< const char16_t *, condition > operator ""_ ## name( const char16_t * lit, size_t ) { return Nop_t< const char16_t *, condition >{ lit }; } \
        inline constexpr Nop_t< const char32_t *, condition > operator ""_ ## name( const char32_t * lit, size_t ) { return Nop_t< const char32_t *, condition >{ lit }; } \
    } } \
    using nop:: T_JOIN( line, __LINE__ )::operator""_ ## name;
  • 主要/测试:
代码语言:javascript
复制
///
/// main / tests
///

#include <cassert>
#include <iostream>
#include <cmath>
#include <type_traits>
#include <vector>

NOP_LITERAL( nop )  // must compile
NOP_FUNCTION( atan )
NOP_LITERAL( nop1, true )
NOP_LITERAL( nop2, false )

int main() {

    static_assert( std::is_standard_layout<Nop_t<int,true>>::value, "Is standard layout" );
    static_assert( std::is_pod<Nop_t<int,true>>::value, "Is standard layout" );

    
    {
        struct S {};
        volatile Nop_t< S, false > n1( S{} );  // volatile to supress warning
        volatile Nop_t< S, false > n2;
        volatile Nop_t< int, false > n3( S{} );
        volatile Nop_t< int, false > n4( "asd" );
        MAYBE_UNUSED( n1 );
        MAYBE_UNUSED( n2 );
        MAYBE_UNUSED( n3 );
        MAYBE_UNUSED( n4 );
    }
    {
        struct S {};
        volatile Nop_t< S, true > n1( S{} );
        volatile Nop_t< S, true > n2;
        Nop_t< int, true > n3a( 1 );
        // Nop_t<int,true> n3b( S{} );
        // Nop_t<int,true> n3c( "asd" );
        Nop_t< std::string, true > n4;
        assert( n3a == 1 );
        assert( n4.empty() );
        MAYBE_UNUSED( n1 );
        MAYBE_UNUSED( n2 );
    }
    {
        Nop_t< float, true > t = 10;
        Nop_t< float, false > f = 10;
        (void) atan( t );
        (void) atan( f );
    }
    {
        Nop_t< std::vector<int>, true > vec1{1,2,3};
        Nop_t< std::vector<int>, false > vec2{1,2,3};
        assert( vec1[2] == 3 );
        (void) vec2[2];  // must compile
    }
    {
        Nop_t< std::vector<int>, true > vec1(3,1);
        Nop_t< std::vector<int>, false > vec2(3,1);
        assert( vec1[2] == 1 );
        MAYBE_UNUSED( vec2 );
    }
    {
        Nop_t< int, true > n = 2;
        int m = n;
        assert( n == 2);
        assert( m == 2);
    }
    {
        struct S {};
        Nop_t< const volatile S, false > s;
        sin( s );
        s.size();
    }
    {
        Nop_t< std::string, true > n1 = "ABC";
        Nop_t< std::string, false > n2 = "ERROR";
        
        std::cout << Nop_t< std::string, true >{"123"} << n1;
        std::cout << Nop_t< std::string, false >{"ERROR"} << n2 << std::endl;
        // Output must be "123ABC"  
    }
    {
        
        std::cout << (Nop_t< std::string, true >)"123";
        std::cout << (Nop_t< std::string, false >)"ERROR" << std::endl;
        // Output must be "123"     
    }
    {
        auto i1 = 123_nop1;
        assert( i1 == 123 );
        
        auto i2 = "ERROR"_nop2;
        std::cout << i2 << std::endl;
        // No output must be produced 
    }
    {
        // must compile
        struct S {};
        Nop_t< int, false > n;
        S s;
        n * 1 * n * n;
        1 * n * 1;
        n % 1 % n % n;
        s % n % s % s ;
        
        n += s += n;
        n -= s -= n -= n;
        n *= n;
        n /= n;
        
        sin( n );
        
        n( 1, 2 );
        n( s );
        
        !n;
        
        (void) (n==s<=n<s!=n>=n>s);
        
        n && n && s || n &= 2;
        
        n = 5;
    }
    {
        Nop_t< double, true > nf(0);
        Nop_t< double, true > nd(0);
        (void) sin( nf );
        (void) sin( nd );
    }
}

我的主要问题是:

  • 这个类的使用真的没有开销吗?我有监督什么吗?
  • 界面合理吗?
  • 文件可以吗?从文档中可以理解类的使用吗?
  • 用户定义的文字是否正确实现?它们也没有任何开销吗?
  • 我在网上没有发现像这门课一样的东西,但是这个想法并不是很有异国情调。所以我想知道:这是一个糟糕的方法吗?
  • 对于Nop_t的定义,是否有比使用辅助函数make_nop更简单的方法?
EN

回答 1

Code Review用户

发布于 2022-01-17 17:03:06

一个明显的问题在于:

#定义_OVR(

以下划线开头的名称,后面跟着大写字母,是为实现的任何目的保留的。因此,此名称在用户头中不可用。

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/273081

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档