【问题标题】:How do I move a vector of objects that contain a unique_ptr as a member?如何移动包含 unique_ptr 作为成员的对象向量?
【发布时间】:2019-12-12 00:33:52
【问题描述】:

我有一个函数,它返回一个包含 std::unique_ptr 作为成员的类的 std::vector。我需要将此向量对象存储在堆上,以便通过 C 风格的 DLL 接口传递它。

请参阅以下代码示例:


#include <iostream>
#include <cstdlib>
#include <memory>
#include <vector>

// I have control over the following two classes
class SomeBigClassWithManyMembers { };

class MyClass
{
    std::unique_ptr<SomeBigClassWithManyMembers> up;

public:
    static const std::vector<MyClass> GetMany()
    {
        // imagine this does lots of work
        return std::vector<MyClass>(50);
    }

    // following code is suggested in https://stackoverflow.com/questions/31430533/move-assignable-class-containing-vectorunique-ptrt but doesn't help
    /*
    MyClass() { }
    MyClass(MyClass&& other): up{std::move(other.up)} { }
    MyClass& operator=(MyClass&& other)
    {
        up = std::move(other.up);
        return *this;
    }
    */
};

// Imagine that I can't change this C-style code - it's a fixed interface provided by someone else
struct NastyCStyleStruct
{
    void* v;
};
void NastyCStyleInterface(NastyCStyleStruct s) { printf("%u", (unsigned int)((std::vector<MyClass>*)s.v)->size()); }

int main()
{
    NastyCStyleStruct s;
    s.v = new std::vector<MyClass>(std::move(MyClass::GetMany()));
    NastyCStyleInterface(s);
    return 0;
}

请注意,在我的实际代码中,向量需要比创建它的函数长(因为这是在 DLL 中完成的),所以要写

auto vec = MyClass::GetMany();
s.v = &vec;

就够了。向量必须存储在堆上。

这里的问题是代码似乎尝试使用MyClass 的(不存在的)复制构造函数。我不明白为什么要调用复制构造函数,因为我明确要求使用std::move 提供移动语义。甚至没有将s.v 初始化为适当大小的新new std::vector 并调用std::move 的三参数版本。

来自 g++ 的错误:

In file included from /usr/include/c++/7/memory:64:0,
                 from stackq.cpp:4:
/usr/include/c++/7/bits/stl_construct.h: In instantiation of ‘void std::_Construct(_T1*, _Args&& ...) [with _T1 = MyClass; _Args = {const MyClass&}]’:
/usr/include/c++/7/bits/stl_uninitialized.h:83:18:   required from ‘static _ForwardIterator std::__uninitialized_copy<_TrivialValueTypes>::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<const MyClass*, std::vector<MyClass> >; _ForwardIterator = MyClass*; bool _TrivialValueTypes = false]’
/usr/include/c++/7/bits/stl_uninitialized.h:134:15:   required from ‘_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<const MyClass*, std::vector<MyClass> >; _ForwardIterator = MyClass*]’
/usr/include/c++/7/bits/stl_uninitialized.h:289:37:   required from ‘_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = __gnu_cxx::__normal_iterator<const MyClass*, std::vector<MyClass> >; _ForwardIterator = MyClass*; _Tp = MyClass]’
/usr/include/c++/7/bits/stl_vector.h:331:31:   required from ‘std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = MyClass; _Alloc = std::allocator<MyClass>]’
stackq.cpp:43:65:   required from here
/usr/include/c++/7/bits/stl_construct.h:75:7: error: use of deleted function ‘MyClass::MyClass(const MyClass&)’
     { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
stackq.cpp:10:7: note: ‘MyClass::MyClass(const MyClass&)’ is implicitly deleted because the default definition would be ill-formed:
 class MyClass
       ^~~~~~~
stackq.cpp:10:7: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = SomeBigClassWithManyMembers; _Dp = std::default_delete<SomeBigClassWithManyMembers>]’
In file included from /usr/include/c++/7/memory:80:0,
                 from stackq.cpp:4:
/usr/include/c++/7/bits/unique_ptr.h:388:7: note: declared here
       unique_ptr(const unique_ptr&) = delete;
       ^~~~~~~~~~

如何修复此代码,以便将矢量对象本身存储在堆上?

【问题讨论】:

  • 复制构造函数被调用是因为GetMany被定义为返回一个const std::vector,而不是一个std::vector,所以std::move实际上不能把它变成一个r值;离开它会违反const-ness,所以复制是唯一的选择。奇怪的是你不需要堆上的vector(尽管它可能会使代码更容易),你只需要停止禁止复制省略/移动语义。
  • 啊,是的,愚蠢的我没有注意到这一点!删除 GetMany 上的 const 现在可以编译代码。
  • @MarekR:OP 的代码似乎打算使用一个奇怪的类 C 接口,所以他们可能没有太多选择。
  • @ShadowRanger 我知道这一点,但是将指向 std::vector 的指针传递给 C API 是绝对错误的(我看不到它可能是正确的场景)。很可能他需要std::vector::data 返回的值。另请注意,他是新手,所以这一定是置换编程的结果。
  • 其实我怀疑XY problem

标签: c++ move stdvector unique-ptr


【解决方案1】:

这里的问题是GetMany 被定义为返回一个const std::vector。即使它是按值返回的,所以如果你改变结果不会产生不必要的副作用,编译器仍然强制执行类型(auto 毕竟只是复制了函数的确切返回类型),因此无法移动从中。因此,与其廉价地复制几个指针/size_t 大小的值(到vector 的内容、大小和容量),它必须进行完整的复制构造,包括复制构造存储在原始vector 中的所有值.由于存储实例中的 unique_ptr 成员,该操作失败。

简单地从返回类型中删除const 应该允许std::move/vector 的移动构造函数完成他们的工作,允许超便宜地提取GetMany 返回的vector 的内容没有任何MyClass 实例的副本(甚至移动)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-04
    • 2020-07-29
    • 2013-04-29
    • 2016-05-11
    • 1970-01-01
    相关资源
    最近更新 更多