向量增长时如何执行移动语义?


91

我有std::vector某类的东西A。该类很简单,定义了复制构造函数 move构造函数。

std::vector<A>  myvec;

如果我用A对象填充矢量(例如使用myvec.push_back(a)),则矢量的大小将增大,使用复制构造函数A( const A&)实例化矢量中元素的新副本。

我能以某种方式强制A使用class的move构造函数代替beging吗?


5
您可以通过使用移动感知矢量实现。
K-ballo

2
您能否更具体地说明如何实现这一目标?
Bertwim van Beest

1
您只需使用移动感知矢量实现。听起来您的标准库实现(顺便说一句)是不移动的。您可以尝试使用Boost提供的移动感知容器。
K-ballo

1
好吧,我使用了gcc 4.5.1,它可以移动。
Bertwim van Beest

在我的代码中,即使move构造函数没有显式的“ noexcept”,它也可以使副本构造函数变为私有。
Arne 2014年

Answers:


125

您需要使用来告知C ++(特别是std::vector)您的move构造函数和destructor不会抛出noexcept。然后,当向量增长时,将调用move构造函数。

这是如何声明和实现受以下方面尊重的移动构造函数的方法std::vector

A(A && rhs) noexcept { 
  std::cout << "i am the move constr" <<std::endl;
  ... some code doing the move ...  
  m_value=std::move(rhs.m_value) ; // etc...
}

如果构造函数不是noexceptstd::vector则不能使用它,因为它不能确保标准所要求的异常保证。

有关标准中所说内容的更多信息,请阅读 C ++ Move语义和异常

感谢Bo暗示它可能与例外有关。还请考虑Kerrek SB的建议并emplace_back在可能的情况下使用。它可以更快(但通常不是),它可以更清晰,更紧凑,但也存在一些陷阱(尤其是对于非显式构造函数)。

编辑,通常是您想要的默认值:移动所有可以移动的内容,复制其余内容。要明确要求,写

A(A && rhs) = default;

这样,您将在可能的情况下获得noexcept:默认的Move构造函数是否定义为noexcept?

请注意,Visual Studio 2015和更早版本的早期版本不支持该功能,即使它支持移动语义。


出于利益考虑,如何将IMPL“知道”是否value_type“的举动男星是noexcept?当调用范围也是noexcept函数时,语言可能会限制函​​数调用候选集?
Lightness Races in Orbit

1
@LightnessRacesinOrbit我认为它只是在做诸如en.cppreference.com/w/cpp/types/is_move_constructible之类的事情。只能有一个move构造函数,因此应在声明中明确定义。
约翰·伦德伯格

@LightnessRacesinOrbit,从那以后我就知道没有任何一种(标准/有用的)方法可以真正知道是否存在一个noexcept移动构造函数。is_nothrow_move_constructible如果有nothrow复制构造函数,则为true 。我不知道任何昂贵的nothrow拷贝构造函数的实际情况,因此尚不清楚它是否真正重要。
约翰·伦德伯格

对我不起作用。我的析构函数,move构造函数和move分配函数都noexcept在标头和实现中都标记了,当我执行push_back(std:; move)时,它仍会调用复制构造函数。我在这里扯头发。
AlastairG

1
@Johan我发现了问题。我std::move()打错了push_back()电话。在那些情况下,您非常努力地寻找问题,以至于看不到眼前的明显错误。然后是午餐时间,我忘了删除我的评论。
AlastairG

17

有趣的是,如果移动构造函数和析构函数均为,则gcc 4.7.2的向量仅使用移动构造函数noexcept。一个简单的例子:

struct foo {
    foo() {}
    foo( const foo & ) noexcept { std::cout << "copy\n"; }
    foo( foo && ) noexcept { std::cout << "move\n"; }
    ~foo() noexcept {}
};

int main() {
    std::vector< foo > v;
    for ( int i = 0; i < 3; ++i ) v.emplace_back();
}

这将输出预期的:

move
move
move

但是,当我noexcept从中删除时~foo(),结果是不同的:

copy
copy
copy

我猜这也回答了这个问题


在我看来,其他答案仅涉及move构造函数,而不涉及析构函数必须为noexcept。
Nikola Benes

好吧,应该如此,但事实证明,在gcc 4.7.2中不是。因此,实际上,这个问题特定于gcc。不过,它应该在gcc 4.8.0中修复。请参阅相关的stackoverflow问题
尼古拉·贝恩斯

-1

看来,std::vector在重新分配时强制使用move move语义的唯一方法(对于C ++ 17和早期版本)是删除复制构造函数:)。这样,它将在编译时使用您的move构造函数或进行尝试:)。

在很多规则中,std::vector不得在重新分配时使用move构造函数,但在任何地方都不得使用它。

template<class T>
class move_only : public T{
public:
   move_only(){}
   move_only(const move_only&) = delete;
   move_only(move_only&&) noexcept {};
   ~move_only() noexcept {};

   using T::T;   
};

生活

要么

template<class T>
struct move_only{
   T value;

   template<class Arg, class ...Args, typename = std::enable_if_t<
            !std::is_same_v<move_only<T>&&, Arg >
            && !std::is_same_v<const move_only<T>&, Arg >
    >>
   move_only(Arg&& arg, Args&&... args)
      :value(std::forward<Arg>(arg), std::forward<Args>(args)...)
   {}

   move_only(){}
   move_only(const move_only&) = delete;   
   move_only(move_only&& other) noexcept : value(std::move(other.value)) {};    
   ~move_only() noexcept {};   
};

现场代码

您的T班级必须具有noexcept移动构造函数/辅助运算符和noexcept析构函数。否则会出现编译错误。

std::vector<move_only<MyClass>> vec;

1
不必删除复制构造函数。如果move构造函数为noexcept,则将使用它。
巴尔基

@balki可以使用。Standard现在不需要。这是讨论groups.google.com/a/isocpp.org/forum/…–
tower120
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.