Search code examples
c++c++11c++14move-semanticstype-traits

Does trivial copying and moving operations differ?


Let's look at some trivially move-constructible and (not trivially) copy-constructible (but still copy-constructible) user-defined (class) type A:

struct A
{
    A() = default;
    A(A const &) {}
    A(A &&) = default;
};

Then moving of A (move-construction or move-assignment) literally perfroms the following: a source bitwise copied to a destination, despite of operation's name "moving". During trivial moving right hand side is (formally) not const, but triviality of the whole operation requires (actual) non-mutability of right hand side, isn't it? On my mind it means, that trivial copy-operation and trivial move-operation are exactly the same in their deep nature (in terms of memory, memory-layout, bits etc). Am I right?

If it is so, then I think, if I see trivially move-constructible, but not trivially copy-constructible type in user code, then I evidently see some antipattern. Am I right?

Is there an example of such artificial but usable type, which is not trivially copy-constructible/assignable, but trivially move-constructible/assignable?


Solution

  • Is there a use case where a type could have a trivial copy constructor without having a trivial move constructor? Sure.

    For example, it could be useful to have a pointer wrapper type that will always be empty when moved from. There's no reason for the copy constructor to be non-trivial, but the move constructor would have to set the old value to NULL.

    template<typename T>
    class empty_on_move
    {
      T *ptr_;
    
    public:
      empty_on_move(const empty_on_move&) = default;
      empty_on_move(empty_on_move &&other) : ptr_(other.ptr_) {other.ptr_ = nullptr;}
    ...
    };
    

    empty_on_move doesn't own the object, which is why it's OK to have multiple copies of it. It exists solely to make sure that when you move from it, the pointer is in a well-understood state. As such, is_trivially_copy_constructible<empty_on_move<T>> is true, while is_trivially_move_constructible<empty_on_move<T>> is false.

    It would mainly be for use inside of other classes which want to give pointers that particular behavior. That way, you don't have to explicitly write code into their move constructors/assignments to NULL those fields out.


    That being said, you're really asking the wrong question. Why? Because the answer doesn't matter.

    The only time that the triviality of a copy/move constructor/assignment matters is when you need the type to be Trivially Copyable. It is that property which permits the use of memcpy and such things, not the trivially of the individual operations. The trivially copyable property requires that the copy/move constructor/assignment and destructors all are trivial (in C++14, the requirement is that they can be trivial or deleted, but at least one must be non-deleted).

    If you're writing a wrapper around some type (or writing a sum/product type), and you want to expose the properties of that type, you only need concern yourself with exposing Trivial Copyability. That is, if T (or Ts...) is trivially copyable, then your type should be trivially copyable too.

    But otherwise, you shouldn't feel the need to have a trivial copy constructor just because T does.