题
在重构代码并摆脱所有那些我们现在被教导讨厌的 #defines 的同时,我发现了这种用于计算结构中元素数量的美妙之处:
#define STRUCTSIZE(s) (sizeof(s) / sizeof(*s))
它非常有用,但是可以将其转换为内联函数或模板吗?
好吧,ARRAYSIZE 是一个更好的名字,但这是遗留代码(不知道它来自哪里,它至少有 15 年的历史)所以我“按原样”粘贴它。
解决方案
如前所述,代码实际上计算的是数组中的元素数量,而不是结构体中的元素数量。当我需要时,我会明确地写出 sizeof() 除法。如果我要把它变成一个函数,我想在它的定义中清楚地表明它需要一个数组。
template<typename T,int SIZE>
inline size_t array_size(const T (&array)[SIZE])
{
return SIZE;
}
上面的内容类似于 xtofl的, ,除非它防止传递一个指向它的指针(即指向一个动态分配的数组)并错误地得到错误的答案。
编辑: :简化为 约翰·麦格. 编辑: :排队。
不幸的是,上面没有提供编译时答案(即使编译器在后台将其内联并优化为常量),因此不能用作编译时常量表达式。IE。它不能用作声明静态数组的大小。在 C++0x 下,如果替换关键字,这个问题就会消失 排队 经过 常量表达式 (constexpr 是隐式内联的)。
constexpr size_t array_size(const T (&array)[SIZE])
jwfearn 的 解决方案适用于编译时,但涉及使用 typedef 来有效地“保存”新名称声明中的数组大小。然后通过使用该新名称初始化常量来计算数组大小。在这种情况下,我们也可以从一开始就将数组大小简单地保存为一个常量。
马丁约克的 发布的解决方案也可以在编译时工作,但涉及使用非标准 类型() 操作员。解决这个问题的方法是等待 C++0x 并使用 决定类型 (到那时,人们实际上就不需要它来解决这个问题了,因为我们将拥有 常量表达式)。另一种选择是使用 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
在哪里 富 是数组的名称。
其他提示
到目前为止,还没有人提出一种可移植的方法来获取数组的大小,当您只有数组的实例而不是其类型时。(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++。
- 它失败并显示一条描述性错误消息。
- 它不评估价值。(我想不出这会成为问题的情况,因为函数无法返回数组类型,但安全总比后悔好。)
- 它返回大小作为编译时常量。
我将构造包装到宏中以获得一些不错的语法。如果您想摆脱它,唯一的选择就是手动进行替换。
康泰克的解决方案很干净,但它不能在编译时使用,并且它依赖于编译器优化来防止代码膨胀和函数调用开销。
可以使用仅编译时元函数计算数组大小,运行时成本为零。 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;
}
该解决方案在编译时拒绝非数组类型,因此它不会像宏版本那样被指针混淆。
该宏的名称非常具有误导性 - 如果将数组的名称作为宏参数传入,则宏中的表达式将返回数组中的元素数量。
对于其他类型,如果类型是指针,您将得到或多或少毫无意义的东西,否则您将收到语法错误。
通常该宏的名称类似于 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())。
你的宏命名错误,它应该被称为 ARRAYSIZE。它用于确定大小在编译时固定的数组中元素的数量。这是一种可行的方法:
字符 foo[128];//实际上,作为数组大小,您将具有一些恒定或恒定的表达。
for( 无符号 i = 0;i <structsize(foo);++i){}
使用起来有点脆弱,因为你可能会犯这个错误:
char* foo = 新的 char[128];
for( 无符号 i = 0;i <structsize(foo);++i){}
现在,您将迭代 i = 0 到 < 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 :)
简化@KTC,因为我们在模板参数中有数组的大小:
template<typename T, int SIZE>
int arraySize(const T(&arr)[SIZE])
{
return SIZE;
}
缺点是您将在二进制文件中为每个类型名称、大小组合都有一个副本。
- 函数,无模板函数,有
- 模板,我想是的(但是C++
- 模板不是我的事)
编辑: 来自道格的代码
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++ 来修复它。
我更喜欢[BCS]建议的枚举方法(在 这个宏可以转换为函数吗?)
这是因为您可以在编译器需要编译时间常量的地方使用它。该语言的当前版本不允许您使用编译时常量的函数结果,但我相信这将出现在编译器的下一个版本中:
此方法的问题在于,当与重载“*”运算符的类一起使用时,它不会生成编译时错误(有关详细信息,请参阅下面的代码)。
不幸的是,“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];
}
我认为这并不能真正计算出结构中元素的数量。如果结构被压缩并且您使用了小于指针大小的东西(例如 32 位系统上的 char),那么您的结果是错误的。另外,如果该结构包含一个结构,那么你也错了!
是的,它可以用 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 帖子,了解使用一些轻型模板元编程在编译时对类执行此操作的一种很酷的方法。
正如 JohnMcG 的回答,但是
缺点是您将在二进制文件中为每个类型名称、大小组合都有一个副本。
这就是为什么你要把它变成 排队 模板功能。
这里详细回答一下:数组大小确定第 1 部分和这里:数组大小确定第 2 部分.
对于 C99 风格的可变长度数组,纯宏方法 (sizeof(arr) / sizeof(arr[0])) 似乎是唯一可行的方法。