To support move semantics, should function parameters be taken by unique_ptr, by value, or by rvalue?

后端 未结 4 953
忘了有多久
忘了有多久 2020-12-14 09:49

One of my function takes a vector as a parameter and stores it as a member variable. I am using const reference to a vector as described below.

class Test {
         


        
4条回答
  •  春和景丽
    2020-12-14 10:10

    On the surface, option 2 seems like a good idea since it handles both lvalues and rvalues in a single function. However, as Herb Sutter notes in his CppCon 2014 talk Back to the Basics! Essentials of Modern C++ Style, this is a pessimization for the common case of lvalues.

    If m_items was "bigger" than items, your original code will not allocate memory for the vector:

    // Original code:
    void someFunction(const std::vector& items) {
       // If m_items.capacity() >= items.capacity(),
       // there is no allocation.
       // Copying the strings may still require
       // allocations
       m_items = items;
    }
    

    The copy-assignment operator on std::vector is smart enough to reuse the existing allocation. On the other hand, taking the parameter by value will always have to make another allocation:

    // Option 2:
    // When passing in an lvalue, we always need to allocate memory and copy over
    void someFunction(std::vector items) {
       m_items = std::move(items);
    }
    

    To put it simply: copy construction and copy assignment do not necessarily have the same cost. It's not unlikely for copy assignment to be more efficient than copy construction — it is more efficient for std::vector and std::string .

    The easiest solution, as Herb notes, is to add an rvalue overload (basically your option 3):

    // You can add `noexcept` here because there will be no allocation‡
    void someFunction(std::vector&& items) noexcept {
       m_items = std::move(items);
    }
    

    Do note that the copy-assignment optimization only works when m_items already exists, so taking parameters to constructors by value is totally fine - the allocation would have to be performed either way.

    TL;DR: Choose to add option 3. That is, have one overload for lvalues and one for rvalues. Option 2 forces copy construction instead of copy assignment, which can be more expensive (and is for std::string and std::vector)

    † If you want to see benchmarks showing that option 2 can be a pessimization, at this point in the talk, Herb shows some benchmarks

    ‡ We shouldn't have marked this as noexcept if std::vector's move-assignment operator wasn't noexcept. Do consult the documentation if you are using a custom allocator.
    As a rule of thumb, be aware that similar functions should only be marked noexcept if the type's move-assignment is noexcept

提交回复
热议问题