web-dev-qa-db-ja.com

Cで任意の型を受け入れる動的配列を作成する

任意のデータ型(ユーザー定義のデータ型を含む)で機能する動的配列を保持する構造体を作成する方法を見つけようとしていますが、これまでのところ、これが私が思いついたものです。

_#define Vector(DATATYPE) struct {   DATATYPE* data; size_t size; size_t used; }

typedef Vector(int) int_Vector;

int main(int argc, char* argv[]){
    int_Vector vec;
    return 0;
}
_

これが機能している間、私は疑問に思っていましたが、これは良い習慣ですか?私はこのようなことをするべきですか、それとももっと良い方法がありますか?また、typedef Vector(int) int_vector部分なしでこれを実装する方法もあります。基本的に、c ++がテンプレートを使用するのと同じ方法で配列を使用できるようにする方法で、次のようになります。

_#define Vector(DATATYPE) struct {   DATATYPE* data; size_t size; size_t used; }

int main(int argc, char* argv[]){
    Vector(int) vec;
    return 0;
}
_

主に、非常に多くのtypedefを回避し、すべてを1つの名前にするためです。

19
zee

いいえ、Cにはテンプレートシステムがないため、使用できません。

あなたがしたようにマクロで効果を模倣することができます(かなり賢い解決策)が、それはもちろん少し非標準であり、あなたのコードのユーザーはマクロとその制限を学ぶ必要があります。

通常、Cコードは非常に扱いにくいため、試行しません。

最も「一般的な」典型的なベクトルはglibの GArray のようなものですが、それは各要素のタイプを知っているふりをしません。代わりに、アクセスするときに気にするのはユーザーに任されており、配列は各要素をnバイトとしてモデル化するだけです。

C11には_Generic()があり、少し役立つかもしれませんが、正直なところ、あまり経験がありません。

15
unwind

2つの変数は、メンバーが同じであっても別個の型として定義されているため、2番目の例は機能しません。なぜそうなのか、私の 既存の回答 でカバーされています。

ただし、構文は少し異なるアプローチを使用して同じに保つことができます。

#include <stdlib.h>

#define vector(type)    struct vector_##type

struct vector_int
{
    int* array;
    size_t count;
} ;

int main(void)
{
    vector(int) one = { 0 };
    vector(int) two = { 0 };

    one = two;
    ( void )one ;

    return 0;
}

驚くほどC++のvector<int>に似た使用法と、完全な例を次に示します。

#include <stdlib.h>

#define vector_var(type)    struct vector_##type

struct vector_int
{
    int* array;
    size_t count;
};

void vector_int_Push( struct vector_int* object , int value ) 
{
    //implement it here
}

int vector_int_Pop( struct vector_int* object ) 
{
    //implement it here
    return 0;
}    

struct vector_int_table
{
    void( *Push )( struct vector_int* , int );
    int( *Pop )( struct vector_int* );

} vector_int_table = { 
                         .Push = vector_int_Push ,
                         .Pop = vector_int_Pop 
                     };

#define vector(type)   vector_##type##_table

int main(void)
{
    vector_var(int) one = { 0 };
    vector_var(int) two = { 0 };

    one = two;

    vector(int).Push( &one , 1 );
    int value = vector(int).Pop( &one );
    ( void )value;

    return 0;
}
5
2501

Vector(DATATYPE) struct { DATATYPE* data; size_t size; size_t used; }も、関数へのポインターに対して失敗します。

void*は、任意のobjectへのポインターには十分であり、明確に定義されていますが、関数へのポインターにはそうではありません。

Cでは、あるタイプの関数へのポインターを別のタイプの関数へのポインターとして保存できます。以下の2つのunionを使用することにより、コードには、任意の型へのポインターを保存するための十分なスペースがあります。どのタイプとどのメンバーが使用されたかの管理はオープンのままです。

union u_ptr {
  void *object;
  void (*function)();
}
5
chux

悪くない。そして、私は何の不利益も見ていません。別の方法を説明するために、この場合に最も一般的に使用されるのは、和集合を使用することです。

typedef union { int i; long l; float f; double d; /*(and so on)*/} vdata;
typedef enum  {INT_T,LONG_T,FLOAT_T, /*(and so on)*/} vtype;
typedef struct 
{
    vtype t;
    vdata data
} vtoken;
typedef struct
{
    vtoken *tk;
    size_t sz;
   size_t n;
} Vector;

したがって、これは可能な方法です。データ型の列挙型はtypedefで回避できますが、mixed(例:sum long、to double、floatなど)を使用する場合は、int + doubleがdouble + intと等しくないため、それらを使用する必要があります。組合がこの仕事をしているのを見るのがより簡単なので、これも理由です。すべての算術規則はそのままにしておきます。

2
jurhas

展開 この回答 多型ソリューションに関しては、ポインター型またはユーザー定義型を含めることもできます。このメソッドの主な利点は、「データ型」列挙型を削除し、それを使用してすべてのランタイムチェックスイッチステートメントを削除することです。

variant.h

#ifndef VARIANT_H
#define VARIANT_H

#include <stdio.h>
#include <stdint.h>

typedef void print_data_t (const void* data);
typedef void print_type_t (void);

typedef struct 
{
  void* data;
  print_data_t* print_data;
  print_type_t* print_type;
} variant_t;

void print_data_char    (const void* data);
void print_data_short   (const void* data);
void print_data_int     (const void* data);
void print_data_ptr     (const void* data);
void print_data_nothing (const void* data);

void print_type_char        (void);
void print_type_short       (void);
void print_type_int         (void);
void print_type_int_p       (void);
void print_type_void_p      (void);
void print_type_void_f_void (void);

void print_data (const variant_t* var);
void print_type (const variant_t* var);

#define variant_init(var) {                \
  .data = &var,                            \
                                           \
  .print_data = _Generic((var),            \
    char:  print_data_char,                \
    short: print_data_short,               \
    int:   print_data_int,                 \
    int*:  print_data_ptr,                 \
    void*: print_data_ptr,                 \
    void(*)(void): print_data_nothing),    \
                                           \
  .print_type = _Generic((var),            \
    char:  print_type_char,                \
    short: print_type_short,               \
    int:   print_type_int,                 \
    int*:  print_type_int_p,               \
    void*: print_type_void_p,              \
    void(*)(void): print_type_void_f_void) \
}


#endif /* VARIANT_H */

variant.c

#include "variant.h"

void print_data_char    (const void* data) { printf("%c",  *(const char*)  data); }
void print_data_short   (const void* data) { printf("%hd", *(const short*) data); }
void print_data_int     (const void* data) { printf("%d",  *(const int*)   data); }
void print_data_ptr     (const void* data) { printf("%p",  data); }
void print_data_nothing (const void* data) {}

void print_type_char        (void) { printf("char");          }
void print_type_short       (void) { printf("short");         }
void print_type_int         (void) { printf("int");           }
void print_type_int_p       (void) { printf("int*");          }
void print_type_void_p      (void) { printf("void*");         }
void print_type_void_f_void (void) { printf("void(*)(void)"); }


void print_data (const variant_t* var)
{
  var->print_data(var->data);
}

void print_type (const variant_t* var)
{
  var->print_type();
}

main.c

#include <stdio.h>
#include "variant.h"

int main (void) 
{
  char c = 'A';
  short s = 3;
  int i = 5;
  int* iptr = &i;
  void* vptr= NULL;
  void (*fptr)(void) = NULL;

  variant_t var[] =
  {
    variant_init(c),
    variant_init(s),
    variant_init(i),
    variant_init(iptr),
    variant_init(vptr),
    variant_init(fptr)
  };

  for(size_t i=0; i<sizeof var / sizeof *var; i++)
  {
    printf("Type: ");
    print_type(&var[i]);
    printf("\tData: ");
    print_data(&var[i]);
    printf("\n");
  }

  return 0;
}

出力:

Type: char      Data: A
Type: short     Data: 3
Type: int       Data: 5
Type: int*      Data: 000000000022FD98
Type: void*     Data: 000000000022FDA0
Type: void(*)(void)     Data:

この目的での_Genericの欠点は、型情報を渡すためにマクロとして使用する必要があるため、プライベートカプセル化を使用できないことです。

一方、この場合の「バリアント」は、思いついたすべての新しいタイプに対して維持する必要があるため、それほど実用的または一般的ではありません。

それでも、これらのトリックは、さまざまな同様の目的のために知っておくとよいでしょう。

2
Lundin