【问题标题】:Can this macro be converted to a function?这个宏可以转换成函数吗?
【发布时间】:2008-09-18 18:40:34
【问题描述】:

在重构代码并摆脱所有那些我们现在被教导讨厌的#defines 时,我发现了这种用于计算结构中元素数量的美感:

#define STRUCTSIZE(s) (sizeof(s) / sizeof(*s))

它非常有用,但它可以转换成内联函数或模板吗?

好的,ARRAYSIZE 会是一个更好的名字,但这是遗留代码(不知道它来自哪里,它至少有 15 年的历史)所以我按“原样”粘贴它。

【问题讨论】:

    标签: c++ macros c-preprocessor


    【解决方案1】:

    如前所述,代码实际上计算出数组中元素的数量,而不是结构。我会在需要时明确写出 sizeof() 除法​​。如果我要让它成为一个函数,我想在它的定义中明确说明它需要一个数组。

    template<typename T,int SIZE>
    inline size_t array_size(const T (&array)[SIZE])
    {
        return SIZE;
    }
    

    上面类似于xtofl's,除了它防止传递指向它的指针(表示指向动态分配的数组)并错误地得到错误的答案。

    编辑:根据JohnMcG 进行简化。 编辑:内联。

    不幸的是,上面没有提供编译时答案(即使编译器确实内联并优化它以使其成为引擎盖下的常量),因此不能用作编译时常量表达式。即它不能用作声明静态数组的大小。在 C++0x 下,如果将关键字 inline 替换为 constexpr(constexpr 是隐式内联的),这个问题就会消失。

    constexpr size_t array_size(const T (&array)[SIZE])
    

    jwfearn's 解决方案适用于编译时,但涉及在新名称声明中有效地“保存”数组大小的 typedef。然后通过该新名称初始化一个常量来计算数组大小。在这种情况下,也可以从一开始就简单地将数组大小保存为常量。

    Martin York's 发布的解决方案也可以在编译时工作,但涉及使用非标准的 typeof() 运算符。解决这个问题的方法是等待 C++0x 并使用 decltype (到那时我们实际上不需要它来解决这个问题,因为我们将有 constexpr )。另一种选择是使用 Boost.Typeof,在这种情况下我们将使用

    #include <boost/typeof/typeof.hpp>
    
    template<typename T>
    struct ArraySize
    {
        private:    static T x;
        public:     enum { size = sizeof(T)/sizeof(*x)};
    };
    template<typename T>
    struct ArraySize<T*> {};
    

    并通过书写使用

    ArraySize<BOOST_TYPEOF(foo)>::size
    

    其中 foo 是数组的名称。

    【讨论】:

    • 这对于运行时来说是可以的。需要使用 enum 来允许编译时检查(或等待新版本的 C++ 允许在编译时进行常量函数评估)。
    • 编译时版本怎么样? ;-D
    • 查看 Ben Strassers 出色的解决方案:标准 C++ 版本(不需要 typeof 或 decltype),接受表达式(而不是类型)并在编译时工作。
    【解决方案2】:

    到目前为止,当您只有一个数组的实例而没有它的类型时,没有人提出了一种可移植的方法来获取数组的大小。 (typeof 和 _countof 不可移植,因此无法使用。)

    我会这样做:

    template<int n>
    struct char_array_wrapper{
        char result[n];
    };
    
    template<typename T, int s>
    char_array_wrapper<s> the_type_of_the_variable_is_not_an_array(const T (&array)[s]){
    }
    
    
    #define ARRAYSIZE_OF_VAR(v) sizeof(the_type_of_the_variable_is_not_an_array(v).result)
    
    #include <iostream>
    using namespace std;
    
    int main(){
        int foo[42];
        int*bar;
        cout<<ARRAYSIZE_OF_VAR(foo)<<endl;
        // cout<<ARRAYSIZE_OF_VAR(bar)<<endl;  fails
    }
    
    • 只有值在附近时才有效。
    • 它是可移植的,仅使用 std-C++。
    • 失败并显示描述性错误消息。
    • 它不评估值。 (我想不出这会成为问题的情况,因为数组类型不能由函数返回,但最好是安全而不是抱歉。)
    • 它将大小作为编译时常量返回。

    我将构造包装到一个宏中以获得一些不错的语法。如果您想摆脱它,唯一的选择是手动进行替换。

    【讨论】:

      【解决方案3】:

      KTC 的解决方案是干净的,但不能在编译时使用,它依赖于编译器优化以防止代码膨胀和函数调用开销。

      可以使用仅编译时元函数计算数组大小,运行时成本为零。 BCS 在正确的轨道上,但该解决方案不正确。

      这是我的解决方案:

      // asize.hpp
      template < typename T >
      struct asize; // no implementation for all types...
      
      template < typename T, size_t N >
      struct asize< T[N] > { // ...except arrays
          static const size_t val = N;
      };
      
      template< size_t N  >
      struct count_type { char val[N]; };
      
      template< typename T, size_t N >
      count_type< N > count( const T (&)[N] ) {}
      
      #define ASIZE( a ) ( sizeof( count( a ).val ) ) 
      #define ASIZET( A ) ( asize< A >::val ) 
      

      带有测试代码(使用Boost.StaticAssert 来演示仅编译时的用法):

      // asize_test.cpp
      #include <boost/static_assert.hpp>
      #include "asize.hpp"
      
      #define OLD_ASIZE( a ) ( sizeof( a ) / sizeof( *a ) )
      
      typedef char C;
      typedef struct { int i; double d; } S;
      typedef C A[42];
      typedef S B[42];
      typedef C * PA;
      typedef S * PB;
      
      int main() {
          A a; B b; PA pa; PB pb;
          BOOST_STATIC_ASSERT( ASIZET( A ) == 42 );
          BOOST_STATIC_ASSERT( ASIZET( B ) == 42 );
          BOOST_STATIC_ASSERT( ASIZET( A ) == OLD_ASIZE( a ) );
          BOOST_STATIC_ASSERT( ASIZET( B ) == OLD_ASIZE( b ) );
          BOOST_STATIC_ASSERT( ASIZE( a ) == OLD_ASIZE( a ) );
          BOOST_STATIC_ASSERT( ASIZE( b ) == OLD_ASIZE( b ) );
          BOOST_STATIC_ASSERT( OLD_ASIZE( pa ) != 42 ); // logic error: pointer accepted
          BOOST_STATIC_ASSERT( OLD_ASIZE( pb ) != 42 ); // logic error: pointer accepted
       // BOOST_STATIC_ASSERT( ASIZE( pa ) != 42 ); // compile error: pointer rejected
       // BOOST_STATIC_ASSERT( ASIZE( pb ) != 42 ); // compile error: pointer rejected
          return 0;
      }
      

      此解决方案在编译时拒绝非数组类型,因此不会像宏版本那样被指针混淆。

      【讨论】:

      • 打败我吧:你错过了 typename static const size_t val = sizeof(T) / sizeof( typename remove_extent::type );
      • 很好 - 它比我发布的微软 winnt.h 中的内容更容易理解。很高兴你实际测试了它。我希望我能多次投票赞成。
      • Crud - 我刚刚发现您的模板不是微软的替代品。你的需要一个类型作为参数,微软的需要一个变量。但这仍然会进入我的一组代码 sn-ps。
      【解决方案4】:

      宏有一个非常容易引起误解的名称 - 如果数组的名称作为宏参数传入,则宏中的表达式将返回数组中元素的数量。

      对于其他类型,如果类型是指针,您将获得或多或少无意义的东西,否则您会收到语法错误。

      通常将该宏命名为 NUM_ELEMENTS() 之类的名称或其他名称以表明它的真正用途。在 C 中不能用函数替换宏,但在 C++ 中可以使用模板。

      我使用的版本基于 Microsoft 的 winnt.h 标头中的代码(如果发布此 sn-p 超出合理使用范围,请告诉我):

      //
      // Return the number of elements in a statically sized array.
      //   DWORD Buffer[100];
      //   RTL_NUMBER_OF(Buffer) == 100
      // This is also popularly known as: NUMBER_OF, ARRSIZE, _countof, NELEM, etc.
      //
      #define RTL_NUMBER_OF_V1(A) (sizeof(A)/sizeof((A)[0]))
      
      #if defined(__cplusplus) && \
          !defined(MIDL_PASS) && \
          !defined(RC_INVOKED) && \
          !defined(_PREFAST_) && \
          (_MSC_FULL_VER >= 13009466) && \
          !defined(SORTPP_PASS)
      //
      // RtlpNumberOf is a function that takes a reference to an array of N Ts.
      //
      // typedef T array_of_T[N];
      // typedef array_of_T &reference_to_array_of_T;
      //
      // RtlpNumberOf returns a pointer to an array of N chars.
      // We could return a reference instead of a pointer but older compilers do not accept that.
      //
      // typedef char array_of_char[N];
      // typedef array_of_char *pointer_to_array_of_char;
      //
      // sizeof(array_of_char) == N
      // sizeof(*pointer_to_array_of_char) == N
      //
      // pointer_to_array_of_char RtlpNumberOf(reference_to_array_of_T);
      //
      // We never even call RtlpNumberOf, we just take the size of dereferencing its return type.
      // We do not even implement RtlpNumberOf, we just decare it.
      //
      // Attempts to pass pointers instead of arrays to this macro result in compile time errors.
      // That is the point.
      //
      extern "C++" // templates cannot be declared to have 'C' linkage
      template <typename T, size_t N>
      char (*RtlpNumberOf( UNALIGNED T (&)[N] ))[N];
      
      #define RTL_NUMBER_OF_V2(A) (sizeof(*RtlpNumberOf(A)))
      
      //
      // This does not work with:
      //
      // void Foo()
      // {
      //    struct { int x; } y[2];
      //    RTL_NUMBER_OF_V2(y); // illegal use of anonymous local type in template instantiation
      // }
      //
      // You must instead do:
      //
      // struct Foo1 { int x; };
      //
      // void Foo()
      // {
      //    Foo1 y[2];
      //    RTL_NUMBER_OF_V2(y); // ok
      // }
      //
      // OR
      //
      // void Foo()
      // {
      //    struct { int x; } y[2];
      //    RTL_NUMBER_OF_V1(y); // ok
      // }
      //
      // OR
      //
      // void Foo()
      // {
      //    struct { int x; } y[2];
      //    _ARRAYSIZE(y); // ok
      // }
      //
      
      #else
      #define RTL_NUMBER_OF_V2(A) RTL_NUMBER_OF_V1(A)
      #endif
      
      #ifdef ENABLE_RTL_NUMBER_OF_V2
      #define RTL_NUMBER_OF(A) RTL_NUMBER_OF_V2(A)
      #else
      #define RTL_NUMBER_OF(A) RTL_NUMBER_OF_V1(A)
      #endif
      
      //
      // ARRAYSIZE is more readable version of RTL_NUMBER_OF_V2, and uses
      // it regardless of ENABLE_RTL_NUMBER_OF_V2
      //
      // _ARRAYSIZE is a version useful for anonymous types
      //
      #define ARRAYSIZE(A)    RTL_NUMBER_OF_V2(A)
      #define _ARRAYSIZE(A)   RTL_NUMBER_OF_V1(A)
      

      另外,Matthew Wilson 的书“不完美的 C++”对这里发生的事情进行了很好的处理(第 14.3 节 - 第 211-213 页 - 数组和指针 - dimensionof())。

      【讨论】:

        【解决方案5】:

        您的宏命名错误,应将其命名为 ARRAYSIZE。它用于确定数组中元素的数量,其大小在编译时是固定的。这是它可以工作的一种方式:

        字符 foo[128]; // 实际上,你会 有一些常数或常数 表达式为数组大小。

        for( 无符号 i = 0; i

        使用起来有点脆弱,因为你可能会犯这个错误:

        char* foo = new char[128];

        for( 无符号 i = 0; i

        您现在将迭代 i = 0 到

        【讨论】:

          【解决方案6】:

          与模板类相比,模板函数的类型是自动推断的。你可以更简单地使用它:

          template< typename T > size_t structsize( const T& t ) { 
            return sizeof( t ) / sizeof( *t ); 
          }
          
          
          int ints[] = { 1,2,3 };
          assert( structsize( ints ) == 3 );
          

          但我同意它不适用于结构:它适用于数组。所以我宁愿称它为 Arraysize :)

          【讨论】:

          • 当程序员将一个指向动态分配数组的指针作为参数传递时会发生什么? ;)
          【解决方案7】:

          简化@KTC,因为我们在模板参数中有数组的大小:

          template<typename T, int SIZE>
          int arraySize(const T(&arr)[SIZE])
          {
              return SIZE;
          }
          

          缺点是对于每个类型名、大小组合,您的二进制文件中都会有一个副本。

          【讨论】:

          • 使其内联,编译器很可能会“做正确的事情(tm)”并且只会使用计算出的 SIZE。
          【解决方案8】:
          • 函数,没有模板函数,是的
          • 模板,我是这么认为的(但是C++
          • 模板不是我的事)

          编辑:来自 Doug 的代码

          template <typename T>
          uint32_t StructSize()  // This might get inlined to a constant at compile time
          {
             return sizeof(T)/sizeof(*T);
          }
          
          // or to get it at compile time for shure
          
          class StructSize<typename T>
          {
             enum { result = sizeof(T)/sizeof(*T) };
          }
          

          有人告诉我第二个不起作用。 OTOH之类的东西应该是可行的,我只是没有使用足够的C++来修复它。

          A page on C++ (and D) templates for compile time stuff

          【讨论】:

          • 好主意,这具有在编译时计算的优点,您可能需要在帖子中指定。
          • 请注意,在 C++ 中(与 C 不同),运算符 sizeof() 在编译时计算。这意味着对于模板的每个实例,返回值在编译时是已知的。如果第一个函数被编译器内联,那么它的结果也可能在编译时被评估......
          • @KTC,我手头没有 C++ 编译器,抱歉。
          【解决方案9】:

          我更喜欢 [BCS](in Can this macro be converted to a function?) 建议的枚举方法

          这是因为您可以在编译器需要编译时间常数的地方使用它。该语言的当前版本不允许您将函数结果用于编译时常量,但我相信这会在下一版本的编译器中出现:

          此方法的问题在于,当与重载“*”运算符的类一起使用时,它不会产生编译时错误(有关详细信息,请参见下面的代码)。

          不幸的是,'BCS' 提供的版本没有按预期编译,所以这是我的版本:

          #include <iterator>
          #include <algorithm>
          #include <iostream>
          
          
          template<typename T>
          struct StructSize
          {
              private:    static T x;
              public:      enum { size = sizeof(T)/sizeof(*x)};
          };
          
          template<typename T>
          struct StructSize<T*>
          {
              /* Can only guarantee 1 item (maybe we should even disallow this situation) */
              //public:     enum { size = 1};
          };
          
          struct X
          {
              int operator *();
          };
          
          
          int main(int argc,char* argv[])
          {
              int data[]                                  = {1,2,3,4,5,6,7,8};
              int copy[ StructSize<typeof(data)>::size];
          
              std::copy(&data[0],&data[StructSize<typeof(data)>::size],&copy[0]);
              std::copy(&copy[0],&copy[StructSize<typeof(copy)>::size],std::ostream_iterator<int>(std::cout,","));
          
              /*
               * For extra points we should make the following cause the compiler to generate an error message */
              X   bad1;
              X   bad2[StructSize<typeof(bad1)>::size];
          }
          

          【讨论】:

            【解决方案10】:

            我认为这并不能真正计算出结构中元素的数量。如果结构被打包并且您使用了小于指针大小的东西(例如 32 位系统上的 char),那么您的结果是错误的。另外,如果结构包含结构,你也错了!

            【讨论】:

            • 应该假设宏是正确的,“它的作用”部分可能是错误的
            • char *s = "12345"; structsize(s) = "12345"/sizeof(char) (它使用 *s,而不是 &s。
            【解决方案11】:

            是的,它可以在 C++ 中制作模板

            template <typename T>
            size_t getTypeSize()
            {
               return sizeof(T)/sizeof(*T);
            }
            

            使用:

            struct JibbaJabba
            {
               int int1;
               float f;
            };
            
            int main()
            {
                cout << "sizeof JibbaJabba is " << getTypeSize<JibbaJabba>() << std::endl;
                return 0;
            }
            

            请参阅上面或下面的 BCS 帖子,了解在编译时使用一些轻量级模板元编程对类执行此操作的一种很酷的方法。

            【讨论】:

            • 实际上并没有按预期工作。尝试使用具有 int、float、char 和 short 元素的结构。也不适用于数组大小。
            • 这不应该编译。 sizeof(*JibbaJabba) 对编译器意味着什么? OP 的宏是自动计算数组中元素的数量,而这个模板只是对结构/类定义做一些事情,而不是实际实例化的数组。
            【解决方案12】:

            xtofl 具有查找数组大小的正确答案。查找结构的大小不需要宏或模板,因为 sizeof() 应该很好。

            我同意preprocessor is evil,但在某些情况下它是least evil of the alternatives

            【讨论】:

              【解决方案13】:

              正如 JohnMcG 的回答,但是

              缺点是对于每个类型名、大小组合,您的二进制文件中都会有一个副本。

              这就是为什么你要把它做成一个内联模板函数。

              【讨论】:

                【解决方案14】:

                【讨论】:

                  【解决方案15】:

                  Windows 特定:

                  CRT 专门为此目的提供了宏 _countof()

                  A link to the doc at MSDN

                  【讨论】:

                    【解决方案16】:

                    对于 C99 风格的可变长度数组,看来纯宏方法 (sizeof(arr) / sizeof(arr[0])) 是唯一可行的方法。

                    【讨论】:

                      猜你喜欢
                      • 2021-04-09
                      • 1970-01-01
                      • 2011-01-09
                      • 2015-01-15
                      • 2021-05-13
                      • 1970-01-01
                      • 1970-01-01
                      • 2020-04-24
                      • 2010-09-29
                      相关资源
                      最近更新 更多