为了提高std::vector<T>
的效率,它的底层数组需要预先分配,有时需要重新分配。但是,这需要创建和稍后移动T
类型的对象,并使用复制程序或移动ctor。
我遇到的问题是T
无法复制或移动,因为它包含无法复制或移动的对象(例如atomic
和mutex
)。 (是的,我正在实现一个简单的线程池。)
我想避免使用指针,因为:
有没有办法避免间接水平?
更新:我根据评论和答案中的反馈修正了一些错误的假设并重新措辞。
答案 0 :(得分:18)
首先,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;
或使用标准算法:
#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; } );
}
答案 1 :(得分:14)
然而,这需要使用copy ctor创建类型为T的对象。
从C ++ 11开始,这是不完全正确的,如果你使用std::vector
的构造函数,它将默认构造一些元素,那么你不需要复制或移动构造函数。
因此,如果没有从池中添加或删除任何线程,则可以执行以下操作:
int num = 23;
std::vector<std::mutex> vec(num);
如果要动态添加或删除内容,则必须使用间接。
std::vector
+ std::unique_ptr
std::deque
,它允许您巧妙地使用基于范围的循环或std算法,并避免所有间接。 (仅允许添加)std::list/forward_list
此解决方案类似于第一,但它具有使用基于范围和算法更容易使用的额外好处。如果您只是按顺序访问元素,那么它可能是最好的,因为不支持随机访问。像这样:
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
。
答案 2 :(得分:1)
总结到目前为止提出的建议:
vector<unique_ptr<T>>
- 添加明确的间接级别,并且OP不需要。deque<T>
- 我首先尝试了deque
,但是从中删除对象也不起作用。关于deque
和list
之间差异的See this discussion。解决方案是使用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.0\VC\include\forward_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
,因为该函数的实现不会复制任何内容。
很多功劳归于其他答案。但是,由于它们都不是没有指针的完整解决方案,我决定写下这个答案。
答案 3 :(得分:0)
可以存储<{1}}中没有移动或复制构造函数的元素,但您只需要避免需要元素移动或复制构造函数的方法。几乎 1 任何改变矢量大小的东西(例如std::vector
,push_back
等)。
实际上,这意味着您需要在构造时分配一个固定大小的向量,它将调用对象的默认构造函数,您可以使用赋值来修改它们。这至少可以用于resize()
个对象,可以将其分配给。{/ p>
1 std::atomic<>
是不需要复制/移动构造函数的大小更改方法的唯一示例,因为它永远不需要移动或复制任何元素(毕竟,此操作后向量为空)。当然,在调用它之后,你再也不能再次增长你的零大小的矢量了!