8

コードをリファクタリングし、今では嫌うように教えられている #define をすべて取り除いているときに、構造内の要素の数を計算するために使用されるこの美しさに出くわしました。

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

そのままでも非常に便利ですが、インライン関数やテンプレートに変換できますか?

OK、ARRAYSIZE の方が適切な名前ですが、これはレガシー コード (どこから来たのかわかりません。少なくとも 15 年前のものです) であるため、「そのまま」貼り付けました。

4

16 に答える 16

19

述べたように、コードは実際には構造体ではなく、配列内の要素の数を計算します。必要に応じて、sizeof()除算を明示的に書き出すだけです。それを関数にするとしたら、その定義で配列を期待していることを明確にしたいと思います。

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

上記はxtoflに似ていますが、ポインタを渡して(つまり、動的に割り当てられた配列を指す)、誤って間違った答えを取得するのを防ぐ点が異なります。

編集JohnMcGに従って簡略化。 編集:インライン。

残念ながら、上記はコンパイル時の答えを提供していません(コンパイラがインラインで最適化し、内部で定数になるように最適化した場合でも)。したがって、コンパイル時定数式として使用することはできません。つまり、静的配列を宣言するためのサイズとして使用することはできません。C ++ 0xでは、キーワードinlineconstexprに置き換えると(constexprは暗黙的にインラインになります)、この問題は解消されます。

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

jwfearnのソリューションはコンパイル時に機能しますが、新しい名前の宣言で配列サイズを効果的に「保存」するtypedefを使用する必要があります。次に、その新しい名前を使用して定数を初期化することにより、配列サイズが計算されます。このような場合、最初から配列サイズを定数に保存するだけでもかまいません。

Martin Yorkが投稿したソリューションもコンパイル時に機能しますが、非標準のtypeof()演算子を使用する必要があります。これを回避するには、C ++ 0xを待機し、decltypeを使用します( constexprがあるため、この問題では実際には必要ありません)。もう1つの方法は、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)

また、マシュー・ウィルソンの著書「Imperfect C++」には、ここで何が起こっているかがうまく説明されています (セクション 14.3 - ページ 211-213 - 配列とポインター - 次元 ())。

于 2008-09-18T18:58:58.410 に答える
1
  • 関数、テンプレート関数なし、はい
  • テンプレート、そう思います(ただし、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) };
}

2つ目は動作しないと言われました。OTOHはそれが実行可能であるはずです、私はそれを修正するのに十分なC++を使用していません。

コンパイル時のもののためのC++(およびD)テンプレートのページ

于 2008-09-18T18:42:01.197 に答える
1

マクロの名前が間違っています。ARRAYSIZE という名前にする必要があります。コンパイル時にサイズが固定される配列の要素数を決定するために使用されます。これが機能する方法は次のとおりです。

char foo[ 128 ]; // 実際には、配列サイズとして何らかの定数または定数式を使用します。

for( unsigned i = 0; i < STRUCTSIZE( foo ); ++i ) { }

この間違いを犯す可能性があるため、使用するのはちょっと脆いです:

char* foo = 新しい char[128];

for( unsigned i = 0; i < STRUCTSIZE( foo ); ++i ) { }

i = 0 から < 1 まで反復して、髪を引き裂きます。

于 2008-09-18T18:51:48.100 に答える
1

[BCS](このマクロを関数に変換できますか? で)によって提案された enum メソッドを好みます。

これは、コンパイラがコンパイル時の定数を期待している場所で使用できるためです。現在のバージョンの言語では、コンパイル時の const に関数の結果を使用できませんが、コンパイラの次のバージョンでこれが行われると思います。

このメソッドの問題点は、'*' 演算子をオーバーロードしたクラスで使用すると、コンパイル時エラーが生成されないことです (詳細については、以下のコードを参照してください)。

残念ながら、「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 に答える
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;
}

欠点は、Typename、Size の組み合わせごとに、バイナリにこれのコピーがあることです。

于 2008-09-18T19:32:37.220 に答える
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

Windows固有:

_countof()まさにこの目的のためにCRTによって提供されるマクロがあります。

MSDNのドキュメントへのリンク

于 2008-09-19T08:30:30.550 に答える
0

ここで詳細に回答: アレイサイズの決定パート1 およびここで: アレイサイズの決定パート2

于 2008-09-18T19:59:25.253 に答える
0

JohnMcGの答えとして、しかし

欠点は、Typename、Size の組み合わせごとに、バイナリにこれのコピーがあることです。

そのため、インラインテンプレート関数にします。

于 2008-09-18T19:49:54.747 に答える
0

C99 スタイルの可変長配列の場合、純粋なマクロ アプローチ (sizeof(arr) / sizeof(arr[0])) のみが機能するようです。

于 2008-10-06T17:03:19.337 に答える
0

xtofl には、配列サイズを見つけるための正しい答えがあります。sizeof() は適切に機能するはずなので、構造体のサイズを見つけるためにマクロやテンプレートは必要ありません。

私はプリプロセッサが悪であることに同意しますが、代替手段の中で最も害が少ない場合もあります。

于 2008-09-18T19:28:26.327 に答える