How to enforce move semantics when a vector grows?

后端 未结 3 826
花落未央
花落未央 2020-11-22 01:43

I have a std::vector of objects of a certain class A. The class is non-trivial and has copy constructors and move constructors defined.

相关标签:
3条回答
  • 2020-11-22 02:22

    You need to inform C++ (specifically std::vector) that your move constructor and destructor does not throw, using noexcept. Then the move constructor will be called when the vector grows.

    This is how to declare and implement a move constuctor that is respected by 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...
    }
    

    If the constructor is not noexcept, std::vector can't use it, since then it can't ensure the exception guarantees demanded by the standard.

    For more about what's said in the standard, read C++ Move semantics and Exceptions

    Credit to Bo who hinted that it may have to do with exceptions. Also consider Kerrek SB's advice and use emplace_back when possible. It can be faster (but often is not), it can be clearer and more compact, but there are also some pitfalls (especially with non-explicit constructors).

    Edit, often the default is what you want: move everything that can be moved, copy the rest. To explicitly ask for that, write

    A(A && rhs) = default;
    

    Doing that, you will get noexcept when possible: Is the default Move constructor defined as noexcept?

    Note that early versions of Visual Studio 2015 and older did not support that, even though it supports move semantics.

    0 讨论(0)
  • 2020-11-22 02:26

    Interestingly, gcc 4.7.2's vector only uses move constructor if both the move constructor and the destructor are noexcept. A simple example:

    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();
    }
    

    This outputs the expected:

    move
    move
    move
    

    However, when I remove noexcept from ~foo(), the result is different:

    copy
    copy
    copy
    

    I guess this also answers this question.

    0 讨论(0)
  • 2020-11-22 02:27

    It seems, that the only way (for C++17 and early), to enforce std::vector use move semantics on reallocation is deleting copy constructor :) . In this way it will use your move constructors or die trying, at compile time :).

    There are many rules where std::vector MUST NOT use move constructor on reallocation, but nothing about where it MUST USE it.

    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;   
    };
    

    Live

    or

    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 {};   
    };
    

    Live code

    Your T class must have noexcept move constructor/assigment operator and noexcept destructor. Otherwise you'll get compilation error.

    std::vector<move_only<MyClass>> vec;
    
    0 讨论(0)
提交回复
热议问题