2017-04-27 3 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