2017-04-27 1 views
2

J'ai essayé de créer des références de structure struct simples à certaines valeurs de son parent. Parent est stocké dans unique_ptr, à l'intérieur d'un vecteur. Il est instancié avant d'être déplacé là. Après le mouvement, les références ne sont bien sûr plus valables. J'ai trouvé un moyen de les réintégrer, mais je déteste la solution (c'est montré ci-dessous). Je pensais que le constructeur de mouvement est appelé collection.push_back(std::move(d)), mais ce n'est pas le cas pour Derived. Il pourrait être pour unique_ptr cependant, je ne suis pas sûr de cela. Ma question est - quelle devrait être la façon préférée de faire face à de telles situations? Y a-t-il une meilleure solution que celle que j'ai présentée ci-dessous? Le remplacement d'un constructeur de déplacement de unique_ptr est-il utile? Est-ce une bonne idée? Ou, est-ce même une bonne idée de concevoir des objets de la manière présentée ci-dessous?Déplacer le constructeur et le vecteur de 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; 
} 

Sortie:

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

Vos références s'invalidés par 'points.push_back ({x, y}); ', pas' collection.push_back (std :: move (d)); '. Ce dernier appelle un constructeur de déplacement de 'unique_ptr', mais c'est toujours le même objet' Derived'. – aschepler

+2

De plus, 'Base' a besoin d'un destructeur virtuel. – aschepler

+0

@aschepler merci! J'ai oublié à ce sujet - ajouté. Wow, vous avez raison 'points.push_back ({x, y});', aucune idée de comment j'ai raté cela, merci! – solusipse

Répondre

3

Selon la norme, les références ne doivent pas être invalidées par le déménagement. Le vrai problème est le std::vector::push_back qui invalide tout si la capacité change.

Une solution consiste à utiliser un std::deque parce qu'il n'a jamais référence avec invalident push_back():

#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; 
} 

Sortie:

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 semble être une solution, merci! – solusipse

+1

@solusipse vous pouvez également pré-allouer le vecteur en utilisant 'vector.reserve()'. Si vous ne repoussez pas la capacité, les références ne seront pas invalidées. –

+0

@GuillaumeRacicot merci, c'est une autre chose à considérer. – solusipse

1

Cette ligne:

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

signifie que la nouvelle entrée dans refs se référera aux membres de la dernière entrée points.

Mais la prochaine fois que vous faites points.push_back, cela peut provoquer une réallocation de vecteur qui invalide toutes les références déjà stockées dans refs.

Si vous voulez vraiment persister avec vector<Refs> vous devrez revoir votre code pour faire en sorte que la durée de vie des objets étant appelée est supérieure à la durée de vie du vector<Refs>. Le unique_ptr est un hareng rouge.

+0

Merci, je n'étais pas au courant de ça. Je vais utiliser deque comme Galik l'a conseillé et refactorisé plus tard. – solusipse