2016-06-27 9 views
4

ハッシュマップデータ構造のイテレータインタフェースを設計しています。現在の設計は、次のようになります。定義がない不透明な構造体

// map.h 
typedef struct map_iterator map_iterator; 

// map.c 
struct map_iterator 
{ 
    // Implementation details 
}; 

// client.c 
map *m = map_new(); 
map_iterator *it = map_iterator_new(m); 
void *key, *value; 
while (map_iterator_next(it, &key, &value)) { 
    // Use key, value 
} 
map_iterator_free(it); 

しかし、これはイテレータオブジェクトのヒープ割り当てを必要とし、クライアントは、彼らが完了したら、イテレータを解放するために覚えておく必要があります。私は、スタック上のイテレータを返すmap_iterator_new作る場合は、コードは次のようになります。

// map.h 
typedef struct map_iterator 
{ 
    // Implementation details 
}; 

// client.c 
map *m = map_new(); 
map_iterator it = map_iterator_new(m); 
void *key, *value; 
while (map_iterator_next(&it, &key, &value)) { 
    // Use key, value 
} 

しかし、これは私が(そうでない場合、私は不完全な型のエラーを取得する)クライアントコードにmap_iterator構造体の定義を提供する必要があります。私はこの定義を隠し、宣言だけを提供したいと思います。

これを達成する方法はありますか?基本的に、クライアントコードに "この構造体はXバイトを占有してスタックに割り当てることができますが、メンバーにアクセスする方法は教えていません"と伝える方法を探しています。

編集:標準Cのみ、してください! (コンパイラ拡張/プラットフォーム固有の関数なし)

+0

イテレータの「実装の詳細」には何がありますか? – 2501

+0

@ 2501イテレータ状態。ハッシュマップへのポインタ、現在のバケットインデックス、変更回数など –

+1

イテレータへのポインタを提供して後で解放することは問題ではないと思います。カールのような多くのライブラリもこのようなインタフェースを提供します。私はむしろ2番目のオプションよりも最初のオプションを使用したいと思います。 – ckruczek

答えて

-1

allocaという関数があり、呼び出し元のスタックにメモリを予約します。したがって、コードは次のようになります。

// map.h 
typedef struct map_iterator map_iterator; 
extern int map_iterator_size; 
// map.c 
struct map_iterator 
{ 
    // Implementation details 
}; 
int map_iterator_size = sizeof(struct map_iterator); 
// client.c 
map *m = map_new(); 
map_iterator *it = alloca(map_iterator_size); 
map_iterator_new(m, it); 
void *key, *value; 
while (map_iterator_next(it, &key, &value)) { 
    // Use key, value 
} 
+0

ありがとうございますが、私は厳密にC99規格に準拠したソリューションを探しています。申し訳ありません。 –

+0

'alloca'は、スタック破損の脆弱性への関与の歴史を考えれば、あなたは決して考えてはいけない機能の一つです。さらに、これはAPIの設計とはほとんど関係がありません。これは、APIユーザーが行うコーディングミス/選択であるためです。最後に、不完全な型は 'sizeof()'を持たない。 –

+0

私は 'alloca()'とスタックに直接オブジェクトを持っている間の違いは(スタックの破損の観点からは)見られません。 'sizeof'は完全な定義でその場で計算されます。 – nothrow

0

シリアル化を使用します。

Tのオブジェクトをunsigned charの配列にコピーし、次にT型のオブジェクトにコピーすることができます。そのオブジェクトは元のオブジェクトと同じになります。 Tは任意のオブジェクトのタイプとすることができ、これは自動ストレージ期間(スタック)で行うことができる。

int value = 568; 
unsigned char store[sizeof(int)]; 
memcpy(store , &value , sizeof(int)); 
int other; 
memcpy(&other , store , sizeof(int)); 
assert(other == value), 

これは、(AB)は、ユーザから実装を隠すために使用することができます。 2つの構造体を定義します。実際の実装を定義し、ユーザーには表示されず、可視で、適切なサイズのunsigned charの配列のみを含む構造体を定義します。

implementation.c

#include "implementation.h" 

struct invisible 
{ 
    int element1; 
    char element2 
    float element3; 
    void** element4; 
}; 

_Static_assert(sizeof(struct invisible) <= VISIBLE_SIZE); 

struct visible New(void) 
{ 
    struct invisible i = { 1 , '2' , 3.0F , NULL }; 
    struct visible v = { 0 }; 
    memcpy(&v , &i , sizeof(i)); 
    return v; 
} 

void Next(struct visible* v) 
{ 
    struct invisible i = { 0 }; 
    memcpy(&i , &v , sizeof(i)); 
    i.element1++; //make some changes 
    const int next = i.element; 
    memcpy(&v , &i , sizeof(i)); 
    return next; 
} 

implementation.h

#define VISIBLE_SIZE 24 //make sure it greater or equal to the size of struct invisible 
struct visible 
{ 
    unsigned char[VISIBLE_SIZE]; 
}; 

struct visible New(void); 
int Next(struct visible* v); 

user.c

#include "implementation.h" 

void Func(void) 
{ 
    struct visible v = New(); 
    while(1) 
    { 
     const int next = Next(&v); 
     if(next == 10) 
     { 
       break; 
     } 
     printf("%d\n" , next); 
    } 
} 

この例では、メンバーelement1にしか触れません。実際の実装では、不可視の構造体を自由に変更できます。

関連する問題