push_back/emplace_back将对象的浅表副本复制到另一个向量中

Jra*_*xon 11 c++ python vector

说我有以下代码

class Car {
    public:
        string color;
        string name;
        Car(string c, string n): color(c), name(n){}            
}

int main() {
    vector<Car> collection1;
    vector<Car> collection2;
    collection1.emplace_back("black", "Ford");
    collection1.emplace_back("white", "BMW");
    collection1.emplace_back("yellow", "Audi");

    //Question comes here
    collection2.push_back(collection1[0]);

}
Run Code Online (Sandbox Code Playgroud)

现在我相信这是一个深刻的副本collection1[0].我尝试过使用collection2.emplace_back(move(collection1[0])),但随后数据字段collection1[0]将消失.我只想让这个"黑色福特"存在于两个向量中,并且通过任一向量对这个特定对象所做的更改将反映在两个向量上.

我猜测对于真实对象的向量,此向量的元素占用实际内存.因此,元素collection1必须独立于任何元素collection2.我认为最简单的方法是让collection1collection2成为指针的向量并指向相同的向量Car.但是有没有可能的方法使上面的代码工作,而不使用指针向量.我最终希望将这两个集合返回到前一个函数,因此制作指针向量是没有意义的.

简而言之,我想List.append()在python中模拟该方法.

collection1 = [Car("black", "Ford"),Car("white", "BMW"),Car("yellow", "Audi")]
collection2 = []
collection2.append(collection1[0])
collection2[0].color = "blue" // This affects collection1 as well
Run Code Online (Sandbox Code Playgroud)

Ser*_*sta 13

在C++语言中,标准集合实际上包含对象,而在其他语言(如Python或Java)中,它们实际上包含存储在别处的对象的引用(或指针).但由于C++不包含垃圾收集,因此必须在其他地方明确管理对象的生命周期.

该设计的结果是,为了允许在两个不同的集合中使用相同的对象,您必须使用指针或引用的集合(注意,C++不直接允许引用集合;但是,std::ref为此创建).

根据您的使用情况,您可以使用原始指针(如果已经管理了实际对象的生命周期),或者使用std::shared_ptr内部管理引用计数的智能指针(此处),以确保在最后一个对象被自动销毁时shared_ptr被毁了.这与Python对对象的引用相差不远,前提是您知道对末尾的破坏shared_ptr实际上会破坏对象(*).换句话说,如果你不想让它变成悬空,不要保留任何其他指针或引用它.

或者,如果集合不是对称的 - 也就是说,如果一个集合实际上包含所有对象,而另一个集合只包含对前一个对象的引用 - 参考将是你最好的选择,第二个集合可能是一个std::vector<std::reference_wrapper<Car>>.


根据MvG评论添加.

Python对象和C++ shared_ptr之间可能存在令人讨厌的差异.Python有一个完整的垃圾收集器,它足够聪明,可以检测循环引用,并在没有外部引用时立即销毁循环.例:

>>> b = ['x']
>>> a = ['y']
>>> b.append(a)
>>> a.append(b)
>>> a
['y', ['x', [...]]]
>>> b
['x', ['y', [...]]]
Run Code Online (Sandbox Code Playgroud)

a包含一个ref到b,其中包含一个ref ...

如果a被删除(或超出范围),b仍将包含完整链

>>> del a
>>> b
['x', ['y', [...]]]
Run Code Online (Sandbox Code Playgroud)

但如果a和b都被删除(或超出范围),gc将检测到没有更多的外部引用并将破坏所有内容.

不幸的是,如果你设法建立一个C++对象的循环,std::shared_ptr因为它只使用本地引用计数,每个对象将有一个引用到另一个,它们将永远不会被删除,即使它们将超出范围,这将导致内存泄漏.一个例子:

struct Node {
    int val;
    std::shared_ptr<Node> next;
};

a = make_shared<Node>();  // ref count 1
b = make_shared<Node>();
a.next = std::shared_ptr<Node>(b);
b.next = std::shared_ptr<Node>(a); // ref count 2!
Run Code Online (Sandbox Code Playgroud)

地狱来了:即使a和b都超出范围,引用计数仍然是1,共享指针永远不会删除它们的对象,没有循环引用通常会发生什么.程序员必须明确地处理并打破循环(并禁止它发生).例如,b.next = make_shared<Node>();在b超出范围之前就足够了.

  • 与Python引用相反,提及`shared_ptr`不检测周期可能是个好主意. (3认同)

fro*_*tto 5

既然你提到你不喜欢指针,你可以使用引用,但是向量不能存储引用(因为它们不可复制和可赋值).但是std::reference_wrapper在可复制和可分配对象中包装引用.

std::reference_wrapper是一个类模板,它在可复制的可分配对象中包装引用.它经常被用作在标准容器(例如std::vector)中存储引用的机制,这些容器通常不能保存引用.

来源:http://en.cppreference.com/w/cpp/utility/functional/reference_wrapper

vector<Car> collection1;
collection1.emplace_back("black", "Ford");
collection1.emplace_back("white", "BMW");
collection1.emplace_back("yellow", "Audi");

vector<std::reference_wrapper<Car>> collection2{collection1.begin(),
                                                collection1.end()};
Run Code Online (Sandbox Code Playgroud)

使用这种方式,collection2指的是相同的对象collection1.例如:

collection1[0].name = "frogatto!";
std::cout << collection2[0].get().name;
// prints 'frogatto!'
Run Code Online (Sandbox Code Playgroud)

重要:

请注意,不建议使用这种方式,因为您必须拥有另一个管理插入和移除的实体,并对其执行collection1适当的操作collection2.@ Serge Ballesta的回答比我好.使用std::shared_ptr.尝试爱和拥抱指针:)

  • 这是糟糕的风格和糟糕的代码.一旦`collection1`被改变,`collection2`将包含悬空引用.这是一个严重的问题,应该从设计的根本避免. (4认同)
  • @Walter你是对的,但我不是OP项目的_designer_.我只是假设`collection1`的插入和删除由另一个实体同步和管理. (2认同)