2

例として、リンクリストのような単純なデータ構造を考えてみましょう。Cでは、次のようになります。

struct Node
{
    struct Node *next;
    void *data;
};

void *getLastItem(struct Node*);
...

同じ構造体と関数が欲しいのですが、dataフィールドの型を宣言することで型チェックを改善します。これは常に何かへのポインタになります。使用例:

Node<Thing*> list = getListOfThings();
Thing *t = list->data;
t = getLastItem(list);
...

しかし、通常のテンプレートのように、すべてのタイプのポインターの実装を生成したくありません。言い換えれば、Java、ML、およびその他の言語の汎用型またはパラメトリック型のようなものが必要です。テストとして以下のコードを試しました。型指定されていないCのような部分は、最終的には実装ファイルに入れられますが、テンプレートと関数の宣言はヘッダーファイルに入れられます。それらが最適化され、タイプチェックされることを除いて、Cバージョンとほぼ同じマシンコードが残されると思います。

しかし、私はC ++が得意ではありません...これを改善する方法、またはより慣用的なC ++、おそらくテンプレートの特殊化を使用する方法はありますか?

#include <stdio.h>

struct NodeImpl
{
    NodeImpl *next;
    void *data;
};

void *getLastItemImpl(NodeImpl *list)
{
    printf("getLastItem, non-template implementation.\n");
    return 0;  // not implemented yet
}

template <typename T>
struct Node
{
    Node<T> *next;
    T data;
};

template <typename T>
T getLastItem(Node<T> *list)
{
    return (T)getLastItemImpl((NodeImpl*)list);
}

struct A { };
struct B { };

int main()
{
    Node<A*> *as = new Node<A*>;
    A *a = getLastItem(as);
    Node<B*> *bs = new Node<B*>;
    B *b = getLastItem(bs);

}
4

3 に答える 3

4

これはまさにBoost.PointerContainerその通りです。その実装を確認してください。基本的には、の特殊化を実装し、パラメータを出し入れするvoid*他の実装を行います。static_cast

于 2011-09-20T16:08:51.427 に答える
3
struct Node
{
    struct Node *next;
    void *data;
};

void *getLastItem(struct Node*);
...

これはCでは一般的ですが、C++では一般的ではありません。C ++では、通常、次のようになります。

template<typename T>
struct Node
{
    struct Node *next;
    T data;
};

T& getLastItem(const Node&);
...

重要な違いに注意してください。Cバージョンには実装を共有するための別のレベルの間接参照がありますが、C++バージョンではこれを行う必要はありません。これは、Cバージョンに別のn動的メモリ割り当てがあることを意味します。ここnで、はリスト内のアイテムの数です。通常、各割り当てにはグローバルロックの取得が必要であり、多くの場合、割り当てごとに少なくとも16バイトのオーバーヘッドがあり、メモリマネージャがパーティにもたらすすべてのオーバーヘッドがあることを考えると、C++バージョンの利点は重要ではありません。考慮事項のキャッシュの局所性など。

別の言い方をすればNode<int>、の場合、C ++バージョンはを格納しint、Cバージョンは。int *の動的割り当てとともにを格納しintます。

もちろん、これはリンクリストが90%の確率で恐ろしいデータ構造であることを無視しています。

リンクリストを使用する必要があり、データメンバーに動的割り当てを使用する必要がある場合は、「ポインターをvoid*sに置き換える」という考えは不合理ではありません。ただし、C ++ 11コンパイラ(VS2010、最近のGCCバージョンなど)にアクセスできる場合は、とを使用してポインタ型であることに依存してTいることを表明し、C-ではなくを使用する必要があります。インターフェイスメソッドでのスタイルキャスト。Cスタイルのキャストは誰かにできるようにし、コンパイルしますが、実行時に爆発します。std::is_pointerstatic_assertstatic_castNode<SomeTypeBiggerThanVoidPtr>

于 2011-09-20T16:17:59.710 に答える
1

他の回答やコメントが述べているように、std::forward_listまたは別の既存のライブラリを使用してください。あなたが拒否した場合、これは私がするようになります:

#include <stdio.h>

struct NodeImpl
{
    NodeImpl *next;
    void *data;
public:    
    // we have pointers, so fulfill the rule of three
    NodeImpl() : next(NULL), data(NULL) {}
    ~NodeImpl() {}
    NodeImpl& operator=(const NodeImpl& b) {next = b.next; data = b.data; return *this;}
    // This function now a member.  Also, I defined it.
    void* getLastItem()
    {
        if (next)
            return next->getLastItem();
        return data;
    }
    void* getData() {return data;}
    void setData(void* d) {data = d;}
};

// the template _inherits_ from the impl
template <typename T>
struct Node : public NodeImpl
{
    Node<T> operator=(const Node<T>& b) {NodeImpl::operator=(b);}
    // we "redefine" the members, but they're really just wrappers
    T* getLastItem()
    { return static_cast<T*>(NodeImpl::getLastItem());}

    T* getData() {return static_cast<T*>(NodeImpl::getData());}
    void setData(T* d) {NodeImpl::setData(static_cast<void*>(d));}

    //or, if you prefer directness...
    operator T*() {return static_cast<T*>(NodeImpl::getData());}
    Node<T> operator=(T* d) {NodeImpl::setData(static_cast<void*>(d));}  
};


struct A { };
struct B { };

int main()
{
    Node<A> as;  //why were these heap allocated?  The root can be on the stack
    A *a = as.getLastItem();
    Node<B> bs; //also, we want a each node to point to a B, not a B*
    B *b = bs.getLastItem();

    B* newB = new B;
    bs = newB;  //set the data member
    newB = bs;  //read the data member
}

http://ideone.com/xseYk このオブジェクトはnextまたはデータを実際にはカプセル化しないため、すべてを自分で管理する必要があることに注意してください。

于 2011-09-20T17:29:22.803 に答える