Domanda

Durante il refactoring del codice e liberandomi di tutti quei #defines che ora ci viene insegnato a odiare, mi sono imbattuto in questa bellezza usata per calcolare il numero di elementi in una struttura:

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

Molto utile così com'è, ma può essere convertito in una funzione o modello in linea?

OK, ARRAYSIZE sarebbe un nome migliore ma questo è un codice legacy (non ho idea da dove provenga, ha almeno 15 anni) quindi l'ho incollato "così com'è".

È stato utile?

Soluzione

Come è stato affermato, il codice effettivamente calcola il numero di elementi in un array, non la struttura.Vorrei semplicemente scrivere esplicitamente la divisione sizeof() quando lo desidero.Se dovessi renderlo una funzione, vorrei chiarire nella sua definizione che si aspetta un array.

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

Quanto sopra è simile a xtofl, tranne che impedisce di passare un puntatore ad esso (che dice che punta a un array allocato dinamicamente) e di ottenere per errore la risposta sbagliata.

MODIFICARE:Semplificato come da John McG. MODIFICARE:in linea.

Sfortunatamente, quanto sopra non fornisce una risposta in fase di compilazione (anche se il compilatore lo incorpora e lo ottimizza per essere una costante dietro le quinte), quindi non può essere utilizzato come espressione di costante del tempo di compilazione.cioè.Non può essere utilizzato come dimensione per dichiarare un array statico.In C++0x, questo problema scompare se si sostituisce la parola chiave in linea di constexpr (constexpr è implicitamente in linea).

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

jwfearn's la soluzione funziona in fase di compilazione, ma implica la presenza di un typedef che effettivamente "salva" la dimensione dell'array nella dichiarazione di un nuovo nome.La dimensione dell'array viene quindi calcolata inizializzando una costante tramite quel nuovo nome.In tal caso, si può anche semplicemente salvare la dimensione dell'array in una costante dall'inizio.

Martin York la soluzione pubblicata funziona anche in fase di compilazione, ma implica l'utilizzo di file non standard tipo di() operatore.La soluzione a questo problema è attendere C++0x e utilizzare decltype (a quel punto non ce ne sarebbe più bisogno per questo problema come avremo constexpr).Un'altra alternativa è utilizzare Boost.Typeof, nel qual caso ci ritroveremo con

#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*> {};

e viene utilizzato scrivendo

ArraySize<BOOST_TYPEOF(foo)>::size

Dove pippo è il nome di un array.

Altri suggerimenti

Nessuno ha finora proposto un modo portabile per ottenere la dimensione di un array quando si ha solo un'istanza di un array e non il suo tipo.(typeof e _countof non sono portatili, quindi non possono essere utilizzati.)

Lo farei nel modo seguente:

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
}
  • Funziona quando c'è solo il valore.
  • È portatile e utilizza solo std-C++.
  • Fallisce con un messaggio di errore descrittivo.
  • Non valuta il valore.(Non riesco a immaginare una situazione in cui questo sarebbe un problema perché il tipo di array non può essere restituito da una funzione, ma è meglio prevenire che curare.)
  • Restituisce la dimensione come costante di compilazione.

Ho racchiuso il costrutto in una macro per avere una sintassi decente.Se vuoi sbarazzartene, l'unica opzione è eseguire la sostituzione manualmente.

KTCLa soluzione di è pulita ma non può essere utilizzata in fase di compilazione e dipende dall'ottimizzazione del compilatore per prevenire il sovraccarico del codice e le chiamate di funzioni.

È possibile calcolare la dimensione dell'array con una metafunzione solo in fase di compilazione con costi di runtime pari a zero. BCS era sulla strada giusta ma quella soluzione non è corretta.

Ecco la mia soluzione:

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

con codice di prova (usando Boost.StaticAssert per dimostrare l'utilizzo solo in fase di compilazione):

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

Questa soluzione rifiuta i tipi non array in fase di compilazione, quindi non verrà confusa dai puntatori come fa la versione macro.

La macro ha un nome molto fuorviante: l'espressione nella macro restituirà il numero di elementi in un array se il nome di un array viene passato come parametro della macro.

Per altri tipi otterrai qualcosa di più o meno privo di significato se il tipo è un puntatore oppure otterrai un errore di sintassi.

Di solito quella macro ha un nome come NUM_ELEMENT() o qualcosa per indicare la sua vera utilità.Non è possibile sostituire la macro con una funzione in C, ma in C++ è possibile utilizzare un modello.

La versione che utilizzo è basata sul codice nell'intestazione winnt.h di Microsoft (per favore fatemi sapere se pubblicare questo snippet va oltre il fair use):

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

Inoltre, il libro di Matthew Wilson "Imperfect C++" tratta in modo interessante ciò che sta accadendo qui (Sezione 14.3 - pagina 211-213 - Array e puntatori - dimensionof()).

La tua macro ha un nome errato, dovrebbe chiamarsi ARRAYSIZE.Viene utilizzato per determinare il numero di elementi in un array la cui dimensione è fissa in fase di compilazione.Ecco un modo in cui può funzionare:

char foo[128];// In realtà, avresti un'espressione costante o costante come dimensione dell'array.

for( senza segno i = 0;I <struttura (foo);++i) { }

È piuttosto fragile da usare, perché puoi commettere questo errore:

char* foo = nuovo char[128];

for( senza segno i = 0;I <struttura (foo);++i) { }

Ora ripeterai per i = 0 a < 1 e ti strapperai i capelli.

Il tipo di una funzione template viene dedotto automaticamente, a differenza di quello di una classe template.Puoi usarlo in modo ancora più semplice:

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


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

Ma sono d'accordo che non funziona per le strutture:funziona per gli array.Quindi preferirei chiamarlo Arraysize :)

Semplificando @KTC, poiché abbiamo la dimensione dell'array nell'argomento template:

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

Lo svantaggio è che ne avrai una copia nel tuo file binario per ogni combinazione Typename, Size.

  • funzione, nessuna funzione modello, sì
  • template, penso di sì (ma C++
  • i modelli non fanno per me)

Modificare: Dal codice di 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) };
}

Mi è stato detto che il 2° non funziona.OTOH qualcosa del genere dovrebbe essere realizzabile, semplicemente non uso abbastanza C++ per risolverlo.

Una pagina sui modelli C++ (e D) per materiale in fase di compilazione

Preferisco il metodo enum suggerito da [BCS](in Questa macro può essere convertita in una funzione?)

Questo perché puoi usarlo dove il compilatore si aspetta una costante di tempo di compilazione.La versione attuale del linguaggio non ti consente di utilizzare i risultati delle funzioni per i const del tempo di compilazione, ma credo che ciò arriverà nella prossima versione del compilatore:

Il problema con questo metodo è che non genera un errore in fase di compilazione quando viene utilizzato con una classe che ha sovraccaricato l'operatore '*' (vedere il codice seguente per i dettagli).

Sfortunatamente la versione fornita da 'BCS' non viene compilata come previsto, quindi ecco la mia versione:

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

Non penso che questo risolva davvero il numero di elementi in una struttura.Se la struttura è compatta e hai utilizzato elementi più piccoli della dimensione del puntatore (come i caratteri su un sistema a 32 bit), i risultati saranno errati.Inoltre, se la struttura contiene una struttura, anche tu sbagli!

Sì, può essere creato un modello in C++

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

usare:

struct JibbaJabba
{
   int int1;
   float f;
};

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

Vedi il post di BCS sopra o sotto su un modo interessante per farlo con una classe in fase di compilazione utilizzando una metaprogrammazione di modelli leggeri.

xtofl ha la risposta giusta per trovare la dimensione di un array.Non dovrebbe essere necessaria alcuna macro o modello per trovare la dimensione di una struttura, poiché sizeof() dovrebbe funzionare bene.

Sono d'accordo il il preprocessore è malvagio, ma ci sono occasioni in cui è il meno male delle alternative.

Come la risposta di JohnMcG, ma

Lo svantaggio è che ne avrai una copia nel tuo file binario per ogni combinazione Typename, Size.

Ecco perché lo faresti diventare un in linea funzione modello.

Specifico per Windows:

C'è la macro _countof() forniti dal CRT proprio per questo scopo.

Un collegamento al documento su MSDN

Per gli array a lunghezza variabile in stile C99, sembra che l'approccio macro puro (sizeof(arr) / sizeof(arr[0])) sia l'unico che funzionerà.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top