2016-12-06 24 views
4

はじめにC++インタフェースを書く:私は、従来のCのコードベースを多用しますC++ 11のアプリケーションを書いています。従来のコードで非常に一般的なパターンを構築し、基本的には、コンストラクタ/デストラクタであるよう動的に割り当てられたCの構造体

build_struct(LegacyStruct *L, int arg1, int arg2) 
free_struct(LegacyStruct *L) 

ような方法によって破壊されたいくつかのstruct LegacyStructの存在です。レガシーコードベースでの所有権のモデルは非常にunique_ptr -esqueあるので、私は次のようにメモリ安全、RAII志向のラッパークラスでラップすることを目指して:

class Wrapper { 
public: 
    Wrapper::Wraper() : handle() {} 
    Wrapper::Wrapper(int same_arg1, int same_arg2); 
    Wrapper::Wrapper(const Wrapper &W) = delete; 
    Wrapper::Wrapper(Wrapper &&W) : handle(std::move(W.handle)) {} 
    //copy operator= and move operator= analogously 
private: 
    std::unique_ptr<LegacyStruct, custom_deleter> handle; 

custom_deleter通話this questionの線に沿ってfree_struct、またはLegacyStructstd::default_deleteの部分的な特殊化です。とにかく今のところこれほど良い、私はこれが共通のデザインパターンだと思うし、それは私のニーズによく合う。

私の質問:私はトラブル私は、フォーム再び

typedef struct LegacyNode { 
    int stack_allocated_data; 
    OtherStruct *heap_allocated_data; 
    LegacyNode *next; 
} LegacyNode; 

のリンクリスト型構造を扱っていただく場合には、このパターンを適応を持っています、レガシーコードベース内の所有権モデルがありますunique_ptr -esque:リンクされたリストの唯一の所有権、すなわちそれを適切に解放する責任があります。同様に、対応するfree_node(LegacyNode *N)関数があり、必要に応じてheap_allocated_dataを解放し、ノード自体を解放します。

でも状況はかなり異なります。

build_list(LegacyNode **L, int *count_p, int other_args){ 
    LegacyNode *newnode; 

    //code allocating newnode and populating its fields 

    //...and then: 
    newcut->next = *L; 
    *L = newcut; 
    (*count_p)++; 
} 

のように見え、build_listへの呼び出しには、編集/明確化

int list_count = 0; 
LegacyNode *L = (LegacyNode *) NULL; 

build_list(&L, &list_count, 99); 

のように見える機能があります:build_listは、コードベースの静的、非エクスポートされた関数で、その私がおそらく数回build_listを呼び出す他の関数を呼び出してアクセスしてください。

したがって、のような私は希望すなわち、ヘッドノードとリストの長さを格納ListWrapクラスを作成し、コピーを持っている/上記Wrapperと同一の演算子を移動させ、リスト自体を単独で所有権があり、それができますしかし、私の理解は、スマートポインタが、この場合、オプションではないということであるなど

、移動しますがコピーされないこと。レガシーノードへのスマートポインタとしてhead_nodeを使用すると、&head_node.get()build_listに渡す必要があります。これはスマートポインタの不変条件/所有権を破壊しますか?現状では

は、私のラッパークラスは、ヘッドノードへの生のポインタ、build_listで使用するためにヘッドノードのアドレスを返すメソッドは、リストを反復処理がfree_nodeを呼び出し、デストラクタ、および述語ベースeraseが含まれています特定の要素のみを削除する-typeメソッドもちろん

、リンクリストを変更し、クリアすると、CS-101レベルのものであるが、私はまだ数時間、それを書き込み、あらゆる場所にメモリリークを持つを無駄にするために管理!また、レガシーコードベースには、ほぼ同じ使用法を持つ他のリンクされたリスト構造がいくつかあるので、タイプとデリータに特化したクラステンプレートにすることができればと思っています。具体的な方法。

おかげ

+0

'&head_node.get()'は有効ではありません。C++、あなたが見つかります。 – Yakk

+0

@Yakk本当に、これは私が無邪気にやりたいことかもしれないと言うより良い言葉かもしれませんが、それはひどい考えであり、そのようには許されません! –

答えて

2

しかし、私の理解では、スマートポインタが、この場合、オプションではないということです。レガシーノードへのスマートポインタとしてhead_nodeを使用すると、&head_node.get()build_listに渡す必要があります。これはスマートポインタの不変条件/所有権を破壊しますか?

はい、正しいです。build_listはそのメモリ位置のオブジェクトを上書きし、スマートポインタのメモリを破壊します。しかし、別の方法があります。std::unique_ptrを既存のポインタで構築することができます!

ListWrapの代わりに、build_listオブジェクトを割り当てて、ポインタを取得してRAIIでラップします。ここで

class ListWrap { 
public: 
    ListWrap(LegacyNode* head, int count); 
    //... 
private: 
    std::unique_ptr<LegacyNode, &free_node> handle; 
    int count; 
}; 

ListWrap::ListWrap(LegacyNode* head, int count) : handle{ head }, count{ count } {} 
+1

ありがとう!何らかの理由で、それが構築された後にリストの所有権を取得することは私には起こりませんでしたが、これは私のユースケースにはるかに適しています。 –

1

は、ノードの束です:

struct Nodes { 
    struct DeleteAllNodes { 
    void operator()(LegacyNode* node)const { 
     while (auto cur = node) { 
     node = cur->next; 
     free_node(node); 
     } 
    } 
    }; 
    std::unique_ptr<LegacyNode, DeleteAllNodes> m_nodes; 
}; 

、ここではいくつかの操作です。それらのほとんどのものは、管理物事を保つためには、短い窓を除いて、私はコメントしている:最初の引数としてNodes&を取る

void push_node(Nodes& nodes, int other_args) { 
    int unused = 0; 
    auto* tmp = nodes.m_nodes.get(); 
    build_list(&tmp, &unused, other_args); 
    nodes.m_nodes.release(); // unmanaged 
    nodes.m_nodes.reset(tmp); // everything managed now 
} 
Nodes pop_node(Nodes& nodes) { 
    if (!nodes.m_nodes) return {}; 
    auto* tmp = nodes.m_nodes->next; // unmanaged 
    nodes.m_nodes->next = nullptr; 
    Nodes retval; 
    retval.m_nodes.reset(tmp); // everything managed now 
    std::swap(retval.m_nodes, nodes.m_nodes); 
    return retval; 
} 
void move_single_node(Nodes& dest, Nodes& src) { 
    Assert(src.m_nodes); 
    if (!src.m_nodes) return; 
    Nodes to_push = pop_node(src); 
    LegacyNode** next = &(to_push.m_nodes->next); 
    Assert(!*next); // shouldn't be possible, pop_node returns a single node 
    *next = dest.m_nodes.release(); // unmanaged for a short period 
    dest = std::move(to_push); 
} 
Nodes splice(Nodes backwards, Nodes forwards) { 
    while(backwards.m_nodes) { 
    move_single_node(forwards, backwards); 
    } 
    return forwards; 
} 
template<class F> 
void erase_if(Nodes& nodes, F&& f, Nodes prefix={}) { 
    if (!nodes.m_nodes) { 
    return splice(std::move(prefix), std::move(nodes)); 
    } 
    Nodes tmp = pop_node(nodes); 
    if (!f(*tmp.m_nodes)) { 
    prefix = splice(std::move(tmp), prefix); 
    } 
    erase_if(nodes, std::forward<F>(f), std::move(prefix)); 
} 

ものはNodesの方法することができます。

+0

この回答ありがとうございます。私はコードベースの外から 'build_list 'へのアクセス権を持っていないことを明確にするために私の質問を編集しましたが、私が質問した方法で呼び出す関数で何度か呼び出されるヘルパー関数です。それは私のコードで、特にスプライシング操作と 'pop' /' remove_if'を採用し、おそらくヘッドノードに反復的なデリゲーターを与える有用なアイデアがたくさんあると言われています。 –

関連する問題