如何在std::vector中存储不带复制或移动构造函数的对象
How to store objects without copy or move constructor in std::vector?
为了提高std::vector<T>
的效率,它的底层数组需要预先分配,有时还需要重新分配。然而,这需要创建T
类型的对象,然后使用复制ctor或移动ctor进行移动。
我遇到的问题是T
无法复制或移动,因为它包含无法复制或删除的对象(如atomic
和mutex
)。(是的,我正在实现一个简单的线程池。)
我想避免使用指针,因为:
- 我不需要间接手段,所以我不想要间接手段
- (指针效率较低,增加了复杂性。使用指针会增加内存碎片并降低数据局部性,这可能(但不一定必须)会对性能产生显著影响。不那么重要,但仍然值得考虑。)
这里有没有一种方法可以避免一定程度的间接性?
更新:我修正了一些错误的假设,并根据评论和答案中的反馈重新措辞了这个问题
首先,std::mutex
无法复制或移动,因此您必须使用某种间接方式。
由于您希望将互斥对象存储在向量中,而不是复制它,所以我将使用std::unique_ptr
。
vector<unique_ptr<T>>
不允许某些矢量操作(例如for_each)
我不确定我是否理解那句话。完全有可能为进行测距
std::vector< std::unique_ptr< int > > v;
// fill in the vector
for ( auto & it : v )
std::cout << *it << std::endl;
或者使用std算法:
#include <iostream>
#include <typeinfo>
#include <vector>
#include <memory>
#include <algorithm>
int main()
{
std::vector< std::unique_ptr< int > > v;
v.emplace_back( new int( 3 ) );
v.emplace_back( new int( 5 ) );
std::for_each( v.begin(), v.end(), []( const std::unique_ptr< int > & it ){ std::cout << *it << std::endl; } );
}
然而,这需要使用复制ctor创建T类型的对象。
这并不完全正确,从C++11开始,如果您使用std::vector
的构造函数,它将默认构造许多元素,那么您就不需要复制或移动构造函数。
因此,如果没有线程从池中添加或删除,那么您可以执行以下操作:
int num = 23;
std::vector<std::mutex> vec(num);
如果你想动态地添加或删除东西,那么你必须使用间接方法。
- 使用已建议的
std::vector
+std::unique_ptr
- 使用
std::deque
,这使您可以巧妙地将其与基于范围的for循环或std算法一起使用,并避免所有间接寻址。(只允许添加) - 使用
std::list/forward_list
此解决方案与第一个解决方案类似,但它具有更容易使用基于范围的for和算法的额外好处。如果只按顺序访问元素可能是最好的,因为不支持随机访问
像这样:
std::deque<std::mutex> deq;
deq.emplace_back();
deq.emplace_back();
for(auto& m : deq) {
m.lock();
}
最后要注意的是,std::thread
当然是可移动的,因此您可以将std::vector
+std::vector::emplace_back
与之一起使用。
总结到目前为止提出的内容:
- 使用
vector<unique_ptr<T>>
--添加一个显式间接级别,这是OP不需要的 - 使用
deque<T>
——我第一次尝试deque
,但从中擦除对象也不起作用。参见本讨论deque
和list
之间的差异
解决方案是使用forward_list
,它是一个单链表(如果您想要一个双链表,也可以使用list
)。正如@JanHudec所指出的,vector
(以及它的许多朋友)在添加或删除项目时需要重新分配。这不适合像mutex
和atomic
这样不允许复制或移动的对象。forward_list
和list
不需要这样做,因为每个小区都是独立分配的(我不能引用这方面的标准,但索引方法产生了这种假设)。由于它们实际上是链表,因此不支持随机访问索引。myList.begin() + i
将为您获得第i
个元素的迭代器,但它(最肯定的)必须首先遍历所有以前的i
单元格。
我没有按照标准查看这些承诺,但在Windows(Visual Studio)和CompileOnline(g++)上运行良好。请随意在CompileOnline上玩以下测试用例:
#include <forward_list>
#include <iostream>
#include <mutex>
#include <algorithm>
using namespace std;
class X
{
/// Private copy constructor.
X(const X&);
/// Private assignment operator.
X& operator=(const X&);
public:
/// Some integer value
int val;
/// An object that can be neither copied nor moved
mutex m;
X(int val) : val(val) { }
};
int main()
{
// create list
forward_list<X> xList;
// add some items to list
for (int i = 0; i < 4; ++i)
xList.emplace_front(i);
// print items
for (auto& x : xList)
cout << x.val << endl;
// remove element with val == 1
// WARNING: Must not use remove here (see explanation below)
xList.remove_if([](const X& x) { return x.val == 1; });
cout << endl << "Removed '1'..." << endl << endl;
for (auto& x : xList)
cout << x.val << endl;
return 0;
}
输出:
Executing the program....
$demo
3
2
1
0
Removed '1'...
3
2
0
我预计这将具有与vector<unique_ptr<T>>
大致相同的性能(只要您不经常使用随机访问索引)。
警告:使用forward_list::remove
目前在VS 2012中不起作用。这是因为它在试图删除元素之前复制了它。头文件Microsoft Visual Studio 11.0VCincludeforward_list
(与list
中的问题相同)显示:
void remove(const _Ty& _Val_arg)
{ // erase each element matching _Val
const _Ty _Val = _Val_arg; // in case it's removed along the way
// ...
}
因此,它会被复制,"以防一路上被删除"。这意味着list
和forward_list
甚至不允许存储unique_ptr
。我认为这是一个设计错误。
解决方法很简单:必须使用remove_if
而不是remove
,因为该函数的实现不会复制任何内容。
很多功劳都归于其他答案。然而,由于没有一个是没有指针的完整解决方案,我决定写下这个答案
您可以在std::vector
中存储不带移动或复制构造函数的元素,但您只需避免要求元素具有移动或复制构造器的方法。几乎1任何改变矢量大小的东西(例如,push_back
、resize()
等)。
在实践中,这意味着您需要在构建时分配一个固定大小的向量,它将调用对象的默认构造函数,您可以通过赋值对其进行修改。这至少适用于std::atomic<>
对象,可以将其分配给.
1clear()
是不需要复制/移动构造函数的大小更改方法的唯一示例,因为它从不需要移动或复制任何元素(毕竟,此操作后向量为空)。当然,在调用此命令后,您再也不能增长零大小的向量了!
- 当有分配器意识的容器被复制/移动时,反弹分配器是否被复制/移走
- 为什么复制而不是移动数据元素?
- 使lambda不可复制/不可移动
- 复制和交换习惯用法与移动操作之间的交互
- 为什么调用复制构造函数而不是移动构造函数?
- 隐式移动与复制操作和遏制
- 具有已删除移动和复制构造函数的类的就地构造
- 使用移动和复制语义时函数匹配如何工作?
- 直接初始化不可复制、不可移动的成员,而不使用聚合初始化
- 包装二进制缓冲区 - 可能没有多余的移动/复制?
- 当使用三元运算符并删除移动/复制CTOR时,Visual Studio不执行RVO
- 我们是否应该从派生类调用基类移动复制/分配构造函数
- 具有unique_ptr成员变量的 C++ 移动/复制/赋值<AbstractClass>
- 返回带有已删除移动/复制 ctor 的类型临时
- 消除默认/删除移动/复制语义中涉及的样板的好方法是什么
- C++11:移动/复制构造不明确
- 关于移动/复制c++对象实例
- 我丢失了一个移动/复制赋值
- 如何创建一个不可移动/复制构造的对象数组
- 编译器何时在C++中移动/复制