我设计了一个用于调试构建的类,它在发布模式下不会产生任何开销。例如,usecase是:我有一个函数,我想要计算它被调用的频率。为此,我可以编写以下内容
function void f() {
#ifndef NDEBUG
static int i = 0;
++i;
std::cout << "Counter: " << i << "\n";
#endif
// do something
}但这会让我的源代码受到预处理指令的打击。因此,我有了一个类的想法,上面的例子变成了:
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中编写了测试,并对它们进行了修改,以便在这里发布)。
///
/// 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 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;///
/// 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更简单的方法?发布于 2022-01-17 17:03:06
一个明显的问题在于:
#定义_OVR(
以下划线开头的名称,后面跟着大写字母,是为实现的任何目的保留的。因此,此名称在用户头中不可用。
https://codereview.stackexchange.com/questions/273081
复制相似问题