在重构代码并摆脱我们现在被教导讨厌的所有#defines时,我偶然发现了这种用于计算结构中元素数量的美:
#define STRUCTSIZE(s) (sizeof(s) / sizeof(*s))
非常有用,但可以将其转换为内联函数或模板吗?
好的,ARRAYSIZE会是一个更好的名字,但这是遗留代码(不知道它来自哪里,至少15年)所以我按原样粘贴它。
答案 0 :(得分:19)
如上所述,代码实际上计算出数组中元素的数量,而不是结构。我会在需要时明确写出sizeof()除法。如果我要使它成为一个函数,我想在其定义中明确表示它期望一个数组。
template<typename T,int SIZE>
inline size_t array_size(const T (&array)[SIZE])
{
return SIZE;
}
以上类似于xtofl's,除了它防止传递一个指针(指向动态分配的数组)并错误地得到错误的答案。
编辑:根据JohnMcG简化。 编辑:内联。
不幸的是,上面没有提供编译时答案(即使编译器内联并优化它是引擎盖下的常量),因此不能用作编译时常量表达式。即它不能用作声明静态数组的大小。在C ++ 0x下,如果用 constexpr 替换关键字 inline (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 是数组的名称。
答案 1 :(得分: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;
}
此解决方案在编译时拒绝非数组类型,因此不会像宏版本那样被指针混淆。
答案 2 :(得分: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
}
我将构造包装到宏中以获得一些不错的语法。如果你想摆脱它,你唯一的选择是手动进行替换。
答案 3 :(得分: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的书“Imperfect C ++”对这里发生的事情有一个很好的处理(第14.3节 - 第211-213页 - 数组和指针 - dimensionof())。
答案 4 :(得分:1)
修改:来自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 ++来修复它。
答案 5 :(得分:1)
您的宏名称错误,应该称为ARRAYSIZE。它用于确定在编译时大小固定的数组中的元素数。这是一种可行的方式:
char foo [128]; //实际上,你是 有一些常数或常数 表达式作为数组大小。
for(unsigned i = 0; i&lt; STRUCTSIZE( foo); ++ i){}
使用起来很脆弱,因为你可以犯这个错误:
char * foo = new char [128];
for(unsigned i = 0; i&lt; STRUCTSIZE( foo); ++ i){}
现在,您将迭代i = 0到&lt; 1,撕掉你的头发。
答案 6 :(得分: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:)
答案 7 :(得分:1)
简化@KTC,因为我们在模板参数中有数组的大小:
template<typename T, int SIZE>
int arraySize(const T(&arr)[SIZE])
{
return SIZE;
}
缺点是,对于每个Typename,Size组合,您将在二进制文件中获得此副本。
答案 8 :(得分:1)
我更喜欢[BCS]建议的枚举方法(在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],©[0]);
std::copy(©[0],©[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];
}
答案 9 :(得分: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上面或下面的帖子,了解在编译时使用一些轻模板元编程实现此类的一种很酷的方法。
答案 10 :(得分:0)
我认为这确实不能解决结构中元素的数量问题。如果结构被打包并且您使用的东西小于指针大小(例如32位系统上的char),则结果是错误的。另外,如果结构包含结构,那你也错了!
答案 11 :(得分:0)
xtofl有正确的答案来查找数组大小。
可能不需要宏或模板来查找结构的大小,因为sizeof()应该很好。我同意preprocessor is evil,但有时它是least evil of the alternatives。
答案 12 :(得分:0)
作为JohnMcG的回答,但是
缺点是,对于每个Typename,Size组合,您将在二进制文件中获得此副本。
这就是为什么你要使它成为内嵌模板功能。
答案 13 :(得分:0)
答案 14 :(得分:0)
答案 15 :(得分:0)
对于C99风格的可变长度数组,似乎纯宏方法(sizeof(arr)/ sizeof(arr [0]))是唯一可行的方法。