【问题标题】:Move constructor and vector of unique_ptr移动 unique_ptr 的构造函数和向量
【发布时间】:2017-09-24 14:20:18
【问题描述】:

我尝试创建简单的结构,其中包含对其父项的某些值的引用。父对象存储在unique_ptr 的向量内。它在被移动到那里之前被实例化。运动后,参考当然不再有效。我找到了一种重新实例化它们的方法,但我讨厌这个解决方案(如下所示)。我认为在collection.push_back(std::move(d)) 上调用了移动构造函数,但Derived 并非如此。可能是针对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

【问题讨论】:

  • 您的引用将被points.push_back({x,y}); 无效,而不是collection.push_back(std::move(d));。后者调用unique_ptr的移动构造函数,但它仍然是相同的Derived对象。
  • 另外,Base 需要一个虚拟析构函数。
  • @aschepler 谢谢!我忘了它 - 添加。哇,points.push_back({x,y}); 是对的,不知道我是怎么错过的,谢谢!

标签: c++ c++11 move smart-pointers unique-ptr


【解决方案1】:

根据标准,引用不应因移动而失效。真正的问题是 std::vector::push_back 如果容量发生变化,它会验证所有内容。

一种解决方案是使用std::deque,因为它永远不会使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;
}

输出:

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

【讨论】:

  • @solusipse 您也可以使用vector.reserve() 预先分配向量。如果你不 push_back 超过容量,引用不会失效。
  • @GuillaumeRacicot 谢谢,这是另一件需要考虑的事情。
【解决方案2】:

这一行:

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

表示refs中的新条目将引用points中最后一个条目的成员。

但是下次您执行points.push_back 时,可能会导致向量重新分配,从而使已存储在refs 中的所有引用无效。

如果你真的想坚持使用vector&lt;Refs&gt;,你将不得不重新设计你的代码,以确保被引用对象的生命周期超过vector&lt;Refs&gt;的生命周期。

unique_ptr 是一个红鲱鱼。

【讨论】:

  • 谢谢,我不知道。我将按照 Galik 的建议使用 deque 并稍后重构。
猜你喜欢
  • 1970-01-01
  • 2015-05-25
  • 2017-06-11
  • 1970-01-01
  • 2013-03-27
  • 2017-11-23
  • 2013-12-13
  • 2019-05-17
  • 2021-08-28
相关资源
最近更新 更多