【问题标题】:Moving between two different contiguous containers在两个不同的连续容器之间移动
【发布时间】:2017-12-10 10:54:13
【问题描述】:

我有一个std::vector<double>,我必须转移到boost::container::flat_set<double>。 两个容器都是连续的,因此原则上对向量进行排序后,我可以将数据从一个容器移到另一个容器。

有没有办法在这两个不同的容器之间移动整个数据?

请考虑到我想移动整个数据,而不是逐个元素。

我可以在相同类型的容器之间移动数据,但不能在不同容器之间移动。

std::vector<double>  v1 = ...
std::sort(v1.begin(), v1.end());

std::vector<double>  v2(std::move(v1)); // ok
boost::flat_set<double> f2(v1.begin(), v1.end()); // doesn't move, it copies
boost::flat_set<double> f3(std::move(v1)); // doesn't compile

似乎要让flat_set 工作,应该有一个来自具有.data() 的容器的移动构造函数,其中指针是从参数中窃取的。

【问题讨论】:

  • 可能没有。但由于底层类型是原始类型,memcpy 从一个容器到另一个容器的数据不应该太昂贵。
  • 顺便说一下,复制和移动 POD 是一回事。我已经为您的一般问题给出了一般性答案,但没有读得太深,认为double 只是一个例子。似乎您的问题更具体,我会调用“如何将std::vector 数据注入boost::flat_set?”
  • @Caleth,我认为这只会逐个元素移动,而不是整个数据块

标签: c++11 stl move move-constructor boost-container


【解决方案1】:

我相信有一些方法可以验证两个容器中的数据对齐是否匹配并且可以使用memcpy(并且在不破坏的情况下清除源)存在并且也许有人会与我们分享它,但只要我们想使用STL 有一种方法:std::move_iterator。它使您的容器构造函数移动元素而不是复制。它不会从源容器中移除元素,而是将它们保留为stateless(例如示例中的空字符串)。

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <boost/container/flat_set.hpp>

int main()
{   
    std::vector<std::string>  v1 = {"a","v","d"};
    std::sort(v1.begin(), v1.end());

    std::vector<std::string>  v2(std::move(v1)); // ok
    boost::container::flat_set<std::string> f1(std::make_move_iterator(v2.begin()), std::make_move_iterator(v2.end())); // moves, but does not remove elements from of source container


    for(auto& s : v1)
        std::cout << "'" << s << "'" << ' ';
    std::cout << " <- v1 \n";

    for(auto& s : v2)
        std::cout << "'" << s << "'" << ' ';
    std::cout << " <- v2 \n";

    for(auto& s : f1)
        std::cout << "'" << s << "'" << ' ';
    std::cout << " <- f1 \n";
}

输出

 <- v1 
'' '' ''  <- v2 
'a' 'd' 'v'  <- f1 

在线代码:https://wandbox.org/permlink/ZLbocXKdqYHT0zYi

【讨论】:

  • 谢谢,抱歉,这不是移动数据。它逐个元素移动。在这种情况下移动的其他事情应该是 O(1) 操作。
【解决方案2】:

看起来如果不修改构造函数boost::container::flat 是不可能的。 在不修改任何一个类的情况下,似乎只有 hack 才能做到这一点,例如使用reinterpret_cast。 我找到的解决方案是使用vector 的替代实现或非常丑陋的代码。

在进入我的解决方案之前,我必须说这可能是一个 两个类的缺陷。这些类应该有一组 release()/aquire(start, end)函数分别表示 返回指向释放所有权的数据的指针范围和 从那时起获取拥有它的指针范围。另一种选择可能是 有一个构造函数,该构造函数从任何其他具有 数据成员函数。

使用reinterpret_castvector 的不同实现的解决方案

原来reinterpret_casting 从std::vectorboost::container::flat_set 是不可能的,因为布局不兼容。 但是,可以开箱即用地将 boost::container::vector 重新解释为 boost::container::flat_set(这是因为它们有一个共同的实现)。

#include<cassert>
#include<boost/container/flat_set.hpp>

int main(){
    boost::container::vector<double> v = {1.,2.,3.};
    boost::container::flat_set<double> fs = std::move(reinterpret_cast<boost::container::flat_set<double>&>(v));

    assert(v.size() == 0);
    assert(*fs.find(2.) == 2.);s
    assert(fs.find(4.) == fs.end());
}

所以,我可以将std::vector 替换为boost::container::vector,然后我可以将数据移动到flat_set

使用std::vector 和丑陋代码的非便携式解决方案

std::vectorboost::container::vector的布局不同的原因是boost::container::vector是这样存储元数据的:

class boost::container::vector{
   pointer     m_start;
   size_type   m_size;
   size_type   m_capacity;
}

std::vector(在 GCC 中)基本上是纯指针,

class std::vector{
    pointer _M_start;
    pointer _M_finish;
    pointer _M_end_of_storage;
}

因此,我的结论是,鉴于我使用的 std::vector 的实现与 boost::container::flat_set 不兼容,因此只有通过 hack 才能移动。

在极端情况下,可以这样做(对不起,如果此代码冒犯了某人,该代码不可移植):

template<class T>
boost::container::flat_set<T> to_flat_set(std::vector<T>&& from){
//  struct dummy_vector{T* start; T* finish; T* end_storarge;}& 
//      dfrom = reinterpret_cast<dummy_vector&>(from);
    boost::container::flat_set<T> ret;
    struct dummy_flat_set{T* start; std::size_t size; std::size_t capacity;}& 
        dret = reinterpret_cast<dummy_flat_set&>(ret); 
    dret = {from.data(), from.size(), from.capacity()};
//  dfrom.start = dfrom.finish = dfrom.end_storarge = nullptr;
    new (&from) std::vector<T>();
    return ret;
};

int main(){
    std::vector<double> v = {1.,2.,3.};
    boost::container::flat_set<double> fs = to_flat_set(std::move(v));

    assert(v.size() == 0);
    assert(*fs.find(2.) == 2.);
    assert(fs.find(4.) == fs.end());
}

请注意,我根本没有考虑分配器问题。我不确定如何在这里处理分配器。

回想起来,对于这个特定问题,我不介意使用cast 的形式,因为我必须以某种方式告诉向量在移动到flat_set 之前已排序。 (问题是这变得极端,因为它是reinterpret_cast。) 不过这是次要问题,应该有合法的方式从std::vector 转移到boost::container::vector

【讨论】:

  • reinterpret_cast 是 UB 行为,即使你说没问题。
  • @bolov ,您是在说“开箱即用可以将reinterpret_castboost::container::vectorboost::container::flat_set”这句话。我很好奇,为什么说那是UB?
  • @bolov,确切地说,别名规则,gcc 给出警告。你知道它是否可以在这里工作?您认为将flat_set&lt;T&gt; ret 放入堆中会有帮助吗? (我会放弃 RVO,但这不是主要问题。)
  • 不,无法解决。理想的解决方案是容器具有releaseacquire 方法来访问底层缓冲区和分配器。由于情况并非如此,我建议将std::move(来自算法)与迭代器一起使用。不要为显式调用memcpy 之类的技巧而烦恼。只有is_trivially_copyable 为真的类型才能用memcpy 复制。在合法的情况下,一个体面的编译器会为您完成这些技巧(当然要启用优化)。
  • @bolov,我同意我认为release 应该返回某种指针分配器对或一些带有分配器信息的智能指针。或者一个 shared_ptr/unique_ptr ,其中分配器被类型擦除或其他东西。这就引出了重点,应该有某种指针分配器对类。
猜你喜欢
  • 2012-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-20
  • 2015-04-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多