我有一个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
中都可以使用(并清除而不会破坏)时,都有某种方法可以验证,也许有人会与我们共享,但是只要我们想使用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
在不修改构造函数boost::container::flat
的情况下看起来是不可能的。在不修改任何一个类的情况下,似乎唯一的黑客攻击会这样,例如使用reinterpret_cast
。我发现的解决方案是使用vector
的替代实现或非常丑陋的代码。
进入我的解决方案之前,我必须说这可能是 两个类的缺陷。这些插曲应该有一组
release()
/aquire(start, end)
分别 将指针范围返回到释放所有权的数据和 从那时起,请获取指针范围。另一种选择是 具有一个构造函数,该构造函数从具有A的任何其他容器移动 数据成员功能。
使用reinterpret_cast
和vector
的其他实现的解决方案事实证明,从std::vector
到boost::container::flat_set
是不可能的reinterpret_cast
,因为布局不兼容。但是,可以从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());
}
所以,我可以用boost::container::vector
替换std::vector
,并且可以将数据移至flat_set
。
使用std::vector
和丑陋代码
的不可携带的解决方案 std::vector
和boost::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
不兼容。
在极端情况下,可以做到这一点(对不起,如果此代码冒犯某人,代码不可移植):
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
。