構造体で定義されたCユーザータイプに対して、ある種のデフォルトコンストラクター(C++のような)を作成する方法はありますか?
私はすでに高速イニシャライザのようなマクロを持っています(pthread_mutex
)しかし、もしあなたが宣言時に構造体のいくつかの(またはすべての)フィールドを埋めることができるかどうか知りたかった。
たとえば、pthread_mutex
例、私は欲しい
pthread_mutex_t my_mutex;
と同じ効果を持つ
pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER;
構造体へのポインターを取る初期化関数を作成できます。これは一般的な慣行でした。
また、構造体を作成して初期化する関数(ファクトリーなど)-「クライアント」コードで構造体が「初期化されていない」時間はありません。もちろん-それは人々が慣習に従い、「コンストラクタ」/工場を使用することを前提としています...
mallocまたはfreeでエラーチェックを行わない恐ろしい擬似コード
somestruct* somestruct_factory(/* per haps some initializer agrs? */)
{
malloc some stuff
fill in some stuff
return pointer to malloced stuff
}
void somestruct_destructor(somestruct*)
{
do cleanup stuff and also free pointer
free(somestruct);
}
おそらく誰かがやって来て、初期のC++プリプロセッサ/コンパイラがどのようにこれをすべてCで行うのかを説明するでしょう。
この場合、C++は「クラス」を持たないという点でCと異なります。ただし、C(他の多くの言語と同様)はオブジェクト指向プログラミングに使用できます。この場合、コンストラクターは構造体を初期化する関数にすることができます。これはコンストラクターと同じです(異なる構文のみ)。もう1つの違いは、malloc()(またはいくつかのバリアント)を使用してオブジェクトを割り当てる必要があることです。 C++では、単純に「新しい」演算子を使用します。
例えばC++コード:
class A {
public:
A() { a = 0; }
int a;
};
int main()
{
A b;
A *c = new A;
return 0;
}
同等のCコード:
struct A {
int a;
};
void init_A_types(struct A* t)
{
t->a = 0;
}
int main()
{
struct A b;
struct A *c = malloc(sizeof(struct A));
init_A_types(&b);
init_A_types(c);
return 0;
}
関数「init_A_types」は、C++のコンストラクターとして機能します。
昔はベストプラクティスと考えられていた完全なエンジニアリングソリューションについてお話しましょう。
構造体の問題は、すべてがパブリックであるため、データが非表示にならないことです。
我々はそれを修正することができます。
2つのヘッダーファイルを作成します。 1つは、コードのクライアントが使用する「パブリック」ヘッダーファイルです。次のような定義が含まれています。
typedef struct t_ProcessStruct *t_ProcessHandle;
extern t_ProcessHandle NewProcess();
extern void DisposeProcess(t_ProcessHandle handle);
typedef struct t_PermissionsStruct *t_PermissionsHandle;
extern t_PermissionsHandle NewPermissions();
extern void DisposePermissions(t_PermissionsHandle handle);
extern void SetProcessPermissions(t_ProcessHandle proc, t_PermissionsHandle perm);
次に、次のような定義を含むプライベートヘッダーファイルを作成します。
typedef void (*fDisposeFunction)(void *memoryBlock);
typedef struct {
fDisposeFunction _dispose;
} t_DisposableStruct;
typedef struct {
t_DisposableStruct_disposer; /* must be first */
PID _pid;
/* etc */
} t_ProcessStruct;
typedef struct {
t_DisposableStruct_disposer; /* must be first */
PERM_FLAGS _flags;
/* etc */
} t_PermissionsStruct;
そして、あなたの実装では次のようなことができます:
static void DisposeMallocBlock(void *process) { if (process) free(process); }
static void *NewMallocedDisposer(size_t size)
{
assert(size > sizeof(t_DisposableStruct);
t_DisposableStruct *disp = (t_DisposableStruct *)malloc(size);
if (disp) {
disp->_dispose = DisposeMallocBlock;
}
return disp;
}
static void DisposeUsingDisposer(t_DisposableStruct *ds)
{
assert(ds);
ds->_dispose(ds);
}
t_ProcessHandle NewProcess()
{
t_ProcessHandle proc = (t_ProcessHandle)NewMallocedDisposer(sizeof(t_ProcessStruct));
if (proc) {
proc->PID = NextPID(); /* etc */
}
return proc;
}
void DisposeProcess(t_ProcessHandle proc)
{
DisposeUsingDisposer(&(proc->_disposer));
}
発生するのは、パブリックヘッダーファイルで構造体の前方宣言を行うことです。これで、構造体が不透明になりました。これは、クライアントがそれらを操作できないことを意味します。次に、完全な宣言で、総称的に呼び出すことができるすべての構造体の先頭にデストラクタを含めます。全員に同じdispose関数などに同じmallocアロケーターを使用できます。公開する要素の公開設定/取得関数を作成します。
突然、あなたのコードはもっと正気になりました。アロケーターまたはアロケーターを呼び出す関数からのみ構造体を取得できます。つまり、初期化をボトルネックにすることができます。オブジェクトを破棄できるように、デストラクタを構築します。そして、あなたは行きます。ところで、t_DisposableStructよりも良い名前はt_vTableStructかもしれません。すべての関数ポインターであるvTableStructを使用して、仮想継承を構築できるようになりました。また、vtableの選択要素をオンザフライで変更するなど、純粋なoo言語ではできないことを(通常)行うこともできます。
重要な点は、構造体を安全かつ初期化可能にするためのエンジニアリングパターンisがあることです。
いいえ、直接ではありません。最も近い方法は、インスタンスを割り当てていくつかのフィールドに入力する関数を記述することです。
C構造体を返す関数を作成できます。
struct file create_file(int i, float f) {
struct file obj = { i, f };
// other code here...
return obj;
}
Cで「通常の」メンバー関数を使用できるかどうか疑問に思う場合は、ある程度は可能です。私は最初のオブジェクトとしてのスタイルを好みます。最初の引数として構造体へのポインタを渡します。このようにして、オブジェクトへのインターフェースを定義するいくつかの機能を使用できます。
int file_get_integer(struct file *self) { return self->i; }
float file_get_float(struct file *self) { return self->f; }
そのスタイルで記述した場合、最後にあるのは抽象データ型です。私は、C++で使用されるメンバー関数呼び出し構文を、構造体に関数ポインターを入れて実行することでエミュレートする人を見てきました。
obj.get_integer(&obj);
Linuxカーネルがファイルシステムドライバーへのインターフェイスを定義するために使用します。それは、好きな人も嫌いな人もいるような書き方です。私はそれがあまり好きではありません。なぜなら、私はデータの構造体のメンバーを使用し続け、一般的なオブジェクト指向言語のように見えるメンバー関数呼び出しをエミュレートするためではありません。
基本的に、C++はメソッドのアドレスを含むポインターのリストを作成します。このリストはクラス定義と呼ばれます(クラスdefにはさらにデータがありますが、現時点では無視します)。
純粋なCで「クラス」を持つ一般的なパターンは、「構造クラス」を定義することです。構造体のフィールドの1つは、クラスの「インスタンス」を返すファクトリ関数です。マクロを使用してキャストを非表示にすることをお勧めします。
typedef struct __class * class;
typedef void (*ctor_ptr)(class);
struct class {
char * name;
ctor_ptr ctor;
... destructor and other stuff ...
}
#define NEW(clz) ((struct something *)(((struct class *)clz)->ctor(clz)))
これで、所有するクラスごとに「構造クラス」タイプの構造を作成し、そこに格納されているコンストラクターを呼び出すことで、所有するクラスを定義できます。デストラクタなども同様です。
インスタンスのメソッドが必要な場合は、それらをクラス構造に入れ、インスタンス構造内のクラスへのポインターを保持する必要があります。
#define NEW_SOMETHING() ((struct something *)NEW(&something_definition))
#define METHOD(inst, arg) ((struct something_class *)(((struct something *)inst)->clz)->method(inst, arg))
NEW_SOMETHING
は、構造something_definition
に格納されているクラス定義を使用して、「何か」の新しいインスタンスを作成します。 METHODは、このインスタンスで「メソッド」を呼び出します。実際のコードでは、inst
が実際にsomething
のインスタンスであることを確認する必要があることに注意してください(クラスポインターを比較してください)。
継承することは少し注意が必要であり、読者の演習として残しておきます。
純粋なCのC++でできることはすべて実行できることに注意してください。C++コンパイラは、CPUを魔法のように別のものに置き換えません。それを行うにはもっと多くのコードが必要です。
例を見たい場合は、 glib (Gtk +プロジェクトの一部)をチェックしてください。
malloc()
、memcpy()
、およびC99複合リテラルを中心とした小さなマクロマジックを次に示します。
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define new(TYPE, ...) memdup(&(TYPE){ __VA_ARGS__ }, sizeof(TYPE))
void * memdup(const void * obj, size_t size)
{
void * copy = malloc(size);
return copy ? memcpy(copy, obj, size) : NULL;
}
struct point
{
int x;
int y;
};
int main()
{
int * i = new(int, 1);
struct point * p = new(struct point, 2, 3);
printf("%i %i %i", *i, p->x, p->y);
return 0;
}
関数を使用して構造を作成および破棄することについては、すでに説明しました。しかし、あなたはあなたが「デフォルトコンストラクタ」が欲しいと言ったコメントで-あなたはあなたがいくつかの(すべて?)構造体フィールドをデフォルト値に初期化したいと思うと思います。
これは、関数、マクロ、またはミックスのいずれかのコーディング規則を使用してCで行われます。私は通常、次のようなことをします-
struct some_struct {
int a;
float b;
};
#define some_struct_DEFAULT { 0, 0.0f}
struct some_struct *some_struct_create(void) {
struct some_struct *ptr = malloc(sizeof some_struct);
if(!ptr)
return ptr;
*ptr = some_struct_DEFAULT;
return ptr;
}
// (...)
struct some_struct on_stack = some_struct_DEFAULT;
struct some_struct *on_heap = some_struct_create();
休閑は、コンストラクタを使用する単純なプログラムです。関数「default_constructor」は、明示的な関数呼び出しなしでmain内で呼び出されます。
#include <stdio.h>
void __attribute__ ((constructor)) default_constructor()
{
printf("%s\n", __FUNCTION__);
}
int main()
{
printf("%s\n",__FUNCTION__);
return 0;
}
出力:default_constructor main
コンストラクター関数内では、コンストラクター関数にいくつかの初期化ステートメントを含めることができ、最終的にデストラクタで休閑として解放できます。
#include <stdio.h>
#include <stdlib.h>
struct somestruct
{
int empid;
char * name;
};
struct somestruct * str1;
void __attribute__ ((constructor)) a_constructor()
{
str1 = (struct somestruct *) malloc (sizeof(struct somestruct));
str1 -> empid = 30228;
str1 -> name = "Nandan";
}
void __attribute__ ((destructor)) a_destructor()
{
free(str1);
}
int main()
{
printf("ID = %d\nName = %s\n", str1 -> empid, str1 -> name);
return 0;
}
これがお役に立てば幸いです。
Cでこれを行うと仮定すると、C++の構造体に関する質問ではありません。
私が通常やることは、構造体(または他の変数)へのポインターを取得し、必要な値に設定する関数init_whateverを作成するだけです。
グローバル変数(ゼロに初期化されている)の場合、could「初期化済み」フラグを使用して引数なしのコンストラクターをシミュレートし、他の関数にそのフラグをチェックさせます。フラグがゼロの場合、構造体を初期化します。しかし、これが良いアイデアであるかどうかはまったくわかりません。
そして、他の誰かが指摘したように、マクロで恐ろしいことをすることもできます...
C++コンパイラーをご覧ください。ライブラリやマクロを使用してコンストラクターなどをCにボルトで固定しようとするよりも、エレガントで標準的な方法で、Cに似た構文を持つ言語で覚えられるよりも多くのオブジェクト指向機能を提供します。
それがうまくいかない場合は、GObjectをご覧ください。 http://en.wikipedia.org/wiki/GObject 。 GTKとGnomeで使用されるCのオブジェクトシステムであり、「Cのオブジェクト」を11に増やします。
ウィキペディアから:
GLibオブジェクトシステム(GObject)は、ポータブルオブジェクトシステムと透過的なクロスランゲージの相互運用性を提供する(LGPLによってカバーされる)フリーソフトウェアライブラリです。
システムは、コンストラクタ、デストラクタ、単一継承、インターフェイス、仮想パブリックメソッドおよびプライベートメソッドなどをサポートしています。また、C++で同じことを行うよりも退屈で困難です。楽しんで!
あんまり。正しく覚えていれば、クラスに最も近いのは構造です。メモリを割り当てて、フィールドに入力します。このためにジェネリックコンストラクター型を作成する方法がわかりません。理論的には、これを行うマクロを作成できます。しかし、それが本当に価値があるかどうかはわかりません。
いいえ、構造体は大量のデータにすぎません。構造体内で関数を宣言することはできないため、そのためのコンストラクターを作成する方法はありません。