Move Constructors and Static Arrays

14,786

Solution 1

First off, there's a general sort of advice that says you shouldn't write any copy/move constructor, assignment operator or destructor at all if you can help it, and rather compose your class of high-quality components which in turn provide these, allowing the default-generated functions to Do The Right Thing. (The reverse implication is that if you do have to write any one of those, you probably have to write all of them.)

So the question boils down to "which single-responsibility component class can take advantage of move semantics?" The general answer is: Anything that manages a resource. The point is that the move constructor/assigner will just reseat the resource to the new object and invalidate the old one, thus avoiding the (presumed expensive or impossible) new allocation and deep copying of the resource.

The prime example is anything that manages dynamic memory, where the move operation simply copies the pointer and sets the old object's pointer to zero (so the old object's destructor does nothing). Here's a naive example:

class MySpace
{
  void * addr;
  std::size_t len;

public:
  explicit MySpace(std::size_t n) : addr(::operator new(n)), len(n) { }

  ~MySpace() { ::operator delete(addr); }

  MySpace(const MySpace & rhs) : addr(::operator new(rhs.len)), len(rhs.len)
  { /* copy memory */ }

  MySpace(MySpace && rhs) : addr(rhs.addr), len(rhs.len)
  { rhs.len = 0; rhs.addr = 0; }

  // ditto for assignment
};

The key is that any copy/move constructor will do a full copying of the member variables; it is only when those variables are themselves handles or pointers to resources that you can avoid copying the resource, because of the agreement that a moved object is no longer considered valid and that you're free to steal from it. If there's nothing to steal, then there's no benefit in moving.

Solution 2

In this case it's not useful because int has no move-constructors.

However, it could be useful if those were strings instead, for example:

template<unsigned int N>
class Foo {
public:
    // [snip]

    Foo(Foo<N>&& other) {
        // move each element from other._nums to _nums
        std::move(std::begin(other._nums), std::end(other._nums), &_nums[0]);
    }

    // [snip]

private:
    std::string _nums[N];
};

Now you avoid copying strings where a move will do. I'm not sure if a conforming C++11 compiler will generate equivalent code if you omit all the copy-/move-constructors completely, sorry.

(In other words, I'm not sure if std::move is specially defined to do an element-wise move for arrays.)

Solution 3

For the class template you wrote, there's no advantage to take in a move constructor.

There would be an advantage if the member array was allocated dynamically. But with a plain array as a member, there's nothing to optimize, you can only copy the values. There's no way to move them.

Solution 4

Usually, move-semantic is implemented when your class manages resource. Since in your case, the class doesn't manages resource, the move-semantic would be more like copy-semantic, as there is nothing to be moved.

To better understand when move-semantic becomes necessary, consider making _nums a pointer, instead of an array:

template<unsigned int N>
class Foo {
public:
    Foo() 
    {
        _nums = new int[N](); //allocate and zeo-initialized
    }
    Foo(const Foo<N>& other) 
    {
        _nums = new int[N];
        for (int i = 0; i < N; ++i) _nums[i] = other._nums[i];
    }

    Foo(Foo<N>&& other) 
    {
         _nums = other._nums; //move the resource
         other._nums=0; //make it null
    }

    Foo<N> operator=(const Foo<N> & other); //implement it!

    virtual ~Foo() { delete [] _nums; }

private:
    int *_nums;
};
Share:
14,786
Zeenobit
Author by

Zeenobit

Updated on June 19, 2022

Comments

  • Zeenobit
    Zeenobit almost 2 years

    I've been exploring the possibilities of Move Constructors in C++, and I was wondering what are some ways of taking advantage of this feature in an example such as below. Consider this code:

    template<unsigned int N>
    class Foo {
    public:
        Foo() {
            for (int i = 0; i < N; ++i) _nums[i] = 0;
        }
    
        Foo(const Foo<N>& other) {
            for (int i = 0; i < N; ++i) _nums[i] = other._nums[i];
        }
    
        Foo(Foo<N>&& other) {
            // ??? How can we take advantage of move constructors here?
        }
    
        // ... other methods and members
    
        virtual ~Foo() { /* no action required */ }
    
    private:
        int _nums[N];
    };
    
    Foo<5> bar() {
        Foo<5> result;
        // Do stuff with 'result'
        return result;
    }
    
    int main() {
        Foo<5> foo(bar());
        // ...
        return 0;
    }
    

    In this above example, if we trace the program (with MSVC++ 2011), we see that Foo<N>::Foo(Foo<N>&&) is called when constructing foo, which is the desired behaviour. However, if we didn't have Foo<N>::Foo(Foo<N>&&), Foo<N>::Foo(const Foo<N>&) would be called instead, which would do a redundant copy operation.

    My question is, as noted in the code, with this specific example which is using a statically-allocated simple array, is there any way to utilize the move constructor to avoid this redundant copy?