構造体へのポインターを受け取る初期化関数を作成できます。これは一般的な方法でした。
また、構造体を作成して初期化する関数 (ファクトリのように) - したがって、「クライアント」コードで構造体が「初期化されていない」ことは決してありません。もちろん、それは人々が慣例に従い、「コンストラクタ」/ファクトリを使用することを前提としています...
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++ では、単に「new」演算子を使用します。
例えば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));
}
何が起こるかというと、パブリック ヘッダー ファイルで構造体の前方宣言を行うことです。これで、構造体は不透明になりました。これは、クライアントが構造体を操作できないことを意味します。次に、完全な宣言で、一般的に呼び出すことができるすべての構造体の先頭にデストラクタを含めます。すべての人に同じmallocアロケーターを使用して、同じdispose関数などを使用できます。公開したい要素の set/get 関数を public にします。
突然、あなたのコードはより健全になります。アロケーターまたはアロケーターを呼び出す関数からのみ構造体を取得できます。つまり、初期化のボトルネックになる可能性があります。オブジェクトを破棄できるように、デストラクタを組み込みます。そして、あなたが行く。ちなみに、t_DisposableStruct よりも適切な名前は t_vTableStruct かもしれません。すべて関数ポインタである vTableStruct を持つことで、仮想継承を構築できるようになりました。 vtable の select 要素をオンザフライで変更するなど、(通常は) 純粋な oo 言語ではできないことも実行できます。
重要な点は、ある 構造体を安全かつ初期化可能にするエンジニアリング パターン。