如何在std :: vector中存储没有复制或移动构造函数的对象?

时间:2013-12-10 06:37:47

标签: c++ c++11 vector constructor move

为了提高std::vector<T>的效率,它的底层数组需要预先分配,有时需要重新分配。但是,这需要创建和稍后移动T类型的对象,并使用复制程序或移动ctor。

我遇到的问题是T无法复制或移动,因为它包含无法复制或移动的对象(例如atomicmutex)。 (是的,我正在实现一个简单的线程池。)

我想避免使用指针,因为:

  1. 我不需要间接水平,所以我不想要一个。
  2. (指针效率较低,增加了复杂性。使用指针会增加内存碎片并减少数据位置,这可能(但不一定必须)导致明显的性能影响。不是那么重要,但仍值得考虑。)
  3. 有没有办法避免间接水平?

    更新:我根据评论和答案中的反馈修正了一些错误的假设并重新措辞。

4 个答案:

答案 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);

如果要动态添加或删除内容,则必须使用间接。

  1. 使用已提议的std::vector + std::unique_ptr
  2. 使用std::deque,它允许您巧妙地使用基于范围的循环或std算法,并避免所有间接。 (仅允许添加)
  3. 使用std::list/forward_list此解决方案类似于第一,但它具有使用基于范围和算法更容易使用的额外好处。如果您只是按顺序访问元素,那么它可能是最好的,因为不支持随机访问。
  4. 像这样:

    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)

总结到目前为止提出的建议:

  1. 使用vector<unique_ptr<T>> - 添加明确的间接级别,并且OP不需要。
  2. 使用deque<T> - 我首先尝试了deque,但是从中删除对象也不起作用。关于dequelist之间差异的See this discussion
  3. 解决方案是使用forward_list这是一个单链接列表(如果你想要一个双向链表,你可以使用list)。正如@JanHudec所指出的那样,vector(以及它的许多朋友)在添加或删除项目时需要重新分配。对于mutexatomic等不允许复制或移动的对象,这并不适合。 forward_listlist不要求,因为每个单元格都是独立分配的(我不能引用标准,但索引方法会产生这种假设)。由于它们实际上是链接列表,因此它们不支持随机访问索引。 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_listlist中的同样问题)显示:

    void remove(const _Ty& _Val_arg)
    {   // erase each element matching _Val
        const _Ty _Val = _Val_arg;  // in case it's removed along the way
    
        // ...
    }
    

    因此,它被复制“以防万一它被移除”。这意味着listforward_list甚至不允许存储unique_ptr。我认为这是一个设计错误。

    解决方法很简单:您必须使用remove_if而不是remove,因为该函数的实现不会复制任何内容。

    很多功劳归于其他答案。但是,由于它们都不是没有指针的完整解决方案,我决定写下这个答案。

答案 3 :(得分:0)

可以存储<{1}}中没有移动或复制构造函数的元素,但您只需要避免需要元素移动或复制构造函数的方法。几乎 1 任何改变矢量大小的东西(例如std::vectorpush_back等)。

实际上,这意味着您需要在构造时分配一个固定大小的向量,它将调用对象的默认构造函数,您可以使用赋值来修改它们。这至少可以用于resize()个对象,可以将其分配给。{/ p>

1 std::atomic<>是不需要复制/移动构造函数的大小更改方法的唯一示例,因为它永远不需要移动或复制任何元素(毕竟,此操作后向量为空)。当然,在调用它之后,你再也不能再次增长你的零大小的矢量了!