2017-04-27 17 views
2

親のいくつかの値への参照を保持する単純な構造体を作成しようとしました。親はベクトル内にunique_ptr内に格納されます。そこに移動する前にインスタンス化されます。移動後、参照はもはや有効ではありません。私はそれらを再インスタンス化する方法を見つけましたが、私は解決策が嫌いです(下記参照)。私は移動コンストラクタがcollection.push_back(std::move(d))で呼び出されたと思ったが、それはDerivedの場合ではない。それはunique_ptrのためかもしれませんが、私はそれについては分かりません。 私の質問は、このような状況に対処するにはどうすればよいでしょうか?私が以下に示したよりよい解決策がありますか?移動コンストラクタをオーバーライドするとunique_ptrは役に立ちますか?これは良いアイデアですか?あるいは、以下のようにオブジェクトを設計することは良い考えですか?unique_ptrのコンストラクタとベクトルを移動

#include <iostream> 
#include <vector> 
#include <memory> 


// Inner object of every Base instance, is used to keep reference to 
// Base's inner variables 
struct Ref { 
    Ref(double &x, double &y) 
     : x(x) 
     , y(y) 
    { 

    } 

    std::reference_wrapper<double> x; 
    std::reference_wrapper<double> y; 
}; 


struct Point { 
    double x; 
    double y; 
}; 


struct Base { 
    virtual ~Base() { } 
    // every derived class uses this vector 
    std::vector<Ref> refs; 

    // some meaningless pure virtual method, ignore it 
    virtual void draw() = 0; 
}; 


struct Derived : public Base { 
    Derived() { 
     std::cout << "Derived constructed" << std::endl; 
    } 
    // Method for adding point and relating it with 
    // a reference in refs vector 
    void add(double x, double y) { 
     points.push_back({x, y}); 
     refs.push_back({points.back().x, points.back().y}); 
    } 

    // some meaningless pure virtual method, ignore it 
    virtual void draw() override { } 

    // this vector is specific to this particular derived class 
    std::vector<Point> points; 
}; 


int main() { 

    // some vector for storing objects 
    std::vector<std::unique_ptr<Base>> collection; 

    { 
     auto d = std::unique_ptr<Derived>(new Derived()); 
     d->add(0.01, 0.02); 
     d->add(1.111, 2.222); 
     d->add(14.3333, 3.1414); 
     collection.push_back(std::move(d)); 
    } 

    // posible solution (I hate it) 
    { 
     auto d = std::unique_ptr<Derived>(new Derived()); 
     d->add(0.01, 0.02); 
     d->add(1.111, 2.222); 
     d->add(14.3333, 3.1414); 
     collection.push_back(std::move(d)); 

     auto c = dynamic_cast<Derived *>(collection.back().get()); 
     for (int i = 0; i < c->points.size(); i++) { 
      c->refs[i].x = c->points[i].x; 
      c->refs[i].y = c->points[i].y; 
     } 
    } 

    // Let's take 1st vector element and cast it to Derived 
    { 
     auto d = dynamic_cast<Derived *>(collection[0].get()); 

     std::cout << "values from points vector:" << std::endl; 
     // These work correctly after moving 
     std::cout << d->points[0].x << std::endl; 
     std::cout << d->points[0].y << std::endl; 
     std::cout << d->points[1].x << std::endl; 
     std::cout << d->points[1].y << std::endl; 
     std::cout << d->points[2].x << std::endl; 
     std::cout << d->points[2].y << std::endl; 

     std::cout << "values from refs vector:" << std::endl; 
     // References of course do not work anymore 
     std::cout << d->refs[0].x << std::endl; 
     std::cout << d->refs[0].y << std::endl; 
     std::cout << d->refs[1].x << std::endl; 
     std::cout << d->refs[1].y << std::endl; 
     std::cout << d->refs[2].x << std::endl; 
     std::cout << d->refs[2].y << std::endl; 
    } 

    // Let's take 2nd vector element and cast it to Derived 
    { 
     auto d = dynamic_cast<Derived *>(collection[1].get()); 

     std::cout << "values from points vector:" << std::endl; 
     // These work correctly after moving 
     std::cout << d->points[0].x << std::endl; 
     std::cout << d->points[0].y << std::endl; 
     std::cout << d->points[1].x << std::endl; 
     std::cout << d->points[1].y << std::endl; 
     std::cout << d->points[2].x << std::endl; 
     std::cout << d->points[2].y << std::endl; 

     std::cout << "values from refs vector with ugly fix:" << std::endl; 
     // References of course do not work anymore 
     std::cout << d->refs[0].x << std::endl; 
     std::cout << d->refs[0].y << std::endl; 
     std::cout << d->refs[1].x << std::endl; 
     std::cout << d->refs[1].y << std::endl; 
     std::cout << d->refs[2].x << std::endl; 
     std::cout << d->refs[2].y << std::endl; 
    } 

    return 0; 
} 

出力:

Derived constructed 
Derived constructed 
values from points vector: 
0.01 
0.02 
1.111 
2.222 
14.3333 
3.1414 
values from refs vector: 
0 
0.02 
4.94602e-317 
4.94603e-317 
14.3333 
3.1414 
values from points vector: 
0.01 
0.02 
1.111 
2.222 
14.3333 
3.1414 
values from refs vector with ugly fix: 
0.01 
0.02 
1.111 
2.222 
14.3333 
3.1414 
+2

あなたの参照は 'points.push_back({xで無効になっています、 y}); '、' collection.push_back(std :: move(d)); 'ではありません。後者は 'unique_ptr'の移動コンストラクタを呼び出しますが、依然として同じ' Derived'オブジェクトです。 – aschepler

+2

また、 'Base'には仮想デストラクタが必要です。 – aschepler

+0

@ascheplerありがとう!私はそれを忘れてしまった。うわー、あなたは 'points.push_back({x、y});について正しくあります。 – solusipse

答えて

3

標準によれば、参照は移動によって無効にされるべきではありません。実際の問題はstd::vector::push_backで、容量が変更された場合はすべてを無効にします。

一つの解決策は、それがpush_back()で参照を無効にすることはありませんので、std::dequeを使用することです:

#include <iostream> 
#include <vector> 
#include <deque> 
#include <memory> 

struct Point { 
    double x; 
    double y; 
}; 

struct Base { 
    // every derived class uses this vector 
    std::vector<Point*> refs; 

    // some meaningless pure virtual method, ignore it 
    virtual ~Base() = default; 
    virtual void draw() = 0; 
}; 


struct Derived : public Base { 
    Derived() { 
     std::cout << "Derived constructed" << std::endl; 
    } 
    // Method for adding point and relating it with 
    // a reference in refs vector 
    void add(double x, double y) { 
     points.push_back({x, y}); 
     refs.push_back(&points.back()); 
    } 

    // some meaningless pure virtual method, ignore it 
    void draw() override { } 

    // this vector is specific to this particular derived class 
    std::deque<Point> points; 
}; 


int main() { 

    // some vector for storing objects 
    std::vector<std::unique_ptr<Base>> collection; 

    { 
     auto d = std::unique_ptr<Derived>(new Derived()); 
     d->add(0.01, 0.02); 
     d->add(1.111, 2.222); 
     d->add(14.3333, 3.1414); 
     collection.push_back(std::move(d)); 

     // No ugly fix needed 
    } 

    // Let's take 1st vector element and cast it to Derived 
    { 
     auto d = dynamic_cast<Derived *>(collection[0].get()); 

     std::cout << "values from points vector:" << std::endl; 
     // These work correctly after moving 
     std::cout << d->points[0].x << std::endl; 
     std::cout << d->points[0].y << std::endl; 
     std::cout << d->points[1].x << std::endl; 
     std::cout << d->points[1].y << std::endl; 
     std::cout << d->points[2].x << std::endl; 
     std::cout << d->points[2].y << std::endl; 

     std::cout << "values from refs vector:" << std::endl; 
     // References still work 
     std::cout << d->refs[0]->x << std::endl; 
     std::cout << d->refs[0]->y << std::endl; 
     std::cout << d->refs[1]->x << std::endl; 
     std::cout << d->refs[1]->y << std::endl; 
     std::cout << d->refs[2]->x << std::endl; 
     std::cout << d->refs[2]->y << std::endl; 
    } 

    return 0; 
} 

出力:

Derived constructed 
values from points vector: 
0.01 
0.02 
1.111 
2.222 
14.3333 
3.1414 
values from refs vector: 
0.01 
0.02 
1.111 
2.222 
14.3333 
3.1414 
+0

std :: dequeは解決策のようです、ありがとう! – solusipse

+1

@solusipseあなたは 'vector.reserve()'を使ってベクトルをあらかじめ割り当てることもできます。容量を超えてpush_backしないと、参照は無効になりません。 –

+0

@GuillaumeRacicotありがとう、それは考慮すべきもう一つのことです。 – solusipse

1

このライン:

refs.push_back({points.back().x, points.back().y}); 

refsに新しいエントリがpoints内の最後のエントリのメンバーを参照することを意味します。

次回にpoints.push_backを実行すると、ベクターの再割り当てが発生し、既にrefsに保存されているすべての参照が無効になる可能性があります。

実際にvector<Refs>を使いたい場合は、参照されているオブジェクトの有効期間がvector<Refs>の有効期間を超えるようにコードを再設計する必要があります。

unique_ptrは赤ちゃんです。

+0

ありがとう、私はそれを認識していませんでした。 Galikのアドバイスとリファクタリングの後に、dequeを使うつもりです。 – solusipse

関連する問題