8

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

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

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

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

4

16 回答 16

19

如前所述,代码实际上计算出数组中元素的数量,而不是结构。我会在需要时明确写出 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 隐式为 inline),这个问题就会消失

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

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

Martin York发布的解决方案也在编译时工作,但涉及使用非标准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是数组的名称。

于 2008-09-18T19:15:33.423 回答
5

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;
}

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

于 2008-09-18T22:10:44.830 回答
5

到目前为止,当您只有一个数组的实例而不是它的类型时,没有人提出了一种可移植的方法来获取数组的大小。(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++。
  • 它失败并显示描述性错误消息。
  • 它不评估值。(我想不出这会成为问题的情况,因为数组类型不能由函数返回,但最好是安全而不是抱歉。)
  • 它将大小作为编译时常量返回。

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

于 2008-09-19T10:06:59.380 回答
2

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

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

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

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

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

于 2008-09-18T18:58:58.410 回答
1
  • 函数,没有模板函数,是的
  • 模板,我想是的(但 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++ 来修复它。

用于编译时内容的 C++(和 D)模板页面

于 2008-09-18T18:42:01.197 回答
1

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

字符富[128];// 实际上,您将有一些常量或常量表达式作为数组大小。

for( 无符号 i = 0; i < STRUCTSIZE( foo ); ++i ) { }

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

char* foo = new char[128];

for( 无符号 i = 0; i < STRUCTSIZE( foo ); ++i ) { }

你现在将迭代 i = 0 到 < 1 并撕掉你的头发。

于 2008-09-18T18:51:48.100 回答
1

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

template< typename T > size_t structsize( const T& t ) { 
  return sizeof( t ) / sizeof( *t ); 
}


int ints[] = { 1,2,3 };
assert( structsize( ints ) == 3 );

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

于 2008-09-18T18:54:40.447 回答
1

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

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

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

于 2008-09-18T19:32:37.220 回答
1

我更喜欢 [BCS] 建议的 enum 方法(在Can this macro be convert 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];
}
于 2008-09-18T23:39:01.607 回答
0

是的,它可以在 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 帖子,了解在编译时使用一些轻量级模板元编程对类执行此操作的一种很酷的方法。

于 2008-09-18T18:42:13.723 回答
0

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

于 2008-09-18T18:44:39.630 回答
0

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

我同意预处理器是邪恶的,但在某些情况下它是替代方案中最不邪恶的

于 2008-09-18T19:28:26.327 回答
0

正如约翰麦格的回答,但是

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

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

于 2008-09-18T19:49:54.747 回答
0

在这里详细回答: 数组大小确定第 1 部分 和这里: 数组大小确定第 2 部分

于 2008-09-18T19:59:25.253 回答
0

特定于 Windows:

_countof()CRT 提供了专门用于此目的的宏。

MSDN 上的文档链接

于 2008-09-19T08:30:30.550 回答
0

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

于 2008-10-06T17:03:19.337 回答