Move semantics in MS C++ vs Clang
Asked Answered
C

1

6

After doing some experimentation with move semantics with an array type I created, I am wondering why Microsoft's C++ compiler calls the move constructor when returning from a method by value whilst the Clang compiler elides the copy all together?

Is this correct or incorrect behaviour from Clang? or correct behaviour from Microsoft?

#include <algorithm>
#include <iostream>

template<typename T>
class Array {
    public:
    template<typename E>
    class ArrayIterator {
        public:
        ArrayIterator(Array<E>& elements, int index) : position_(index), elements_(elements) {
        }

        T& operator * () {
            return elements_[position_];
        }

        ArrayIterator& operator++ () {
            position_++;
            return *this;
        }

        ArrayIterator operator++ (int) {
            return ArrayIterator(elements_, ++position_);
        }

        bool operator != (ArrayIterator const & other) {
            return position_ != other.position_;
        }

        private:
        int position_;
        Array<E>& elements_;
    };
    typedef ArrayIterator<T> iterator;
    Array();
    explicit Array(int size);
    ~Array();
    Array(const Array& other);
    Array(Array&& other);
    Array<T>& operator = (Array other);
    T& operator[](int index);
    int size() const;
    iterator begin();
    iterator end();


    private:
    void internal_swap(Array& other);
    T *elements_;
    int length_;
};

template<typename T>
Array<T>::Array() {
    length_ = 0;
    elements_ = 0;
}

template<typename T>
Array<T>::Array(int size) {
    elements_ = new T[size];
    length_ = size;
}

template<typename T>
Array<T>::~Array() {
    delete[] elements_;
    std::cout << "Destroy...." << std::endl;
}

template<typename T>
Array<T>::Array(const Array<T>& other) { 
    std::cout << "copy ctor" << std::endl;

    length_ = other.size();

    T *elements = new T[size()];
    std::copy(other.elements_, other.elements_ + other.size(), elements);

    elements_ = elements;
}

template<typename T>
Array<T>::Array(Array<T>&& other) { 
    std::cout << "move ctor" << std::endl;
    length_ = other.size();
    T* oelements = other.elements_;
    other.elements_ = 0;
    this->elements_ = oelements;

}

template<typename T>
Array<T>& Array<T>::operator = (Array other) {
    internal_swap(other);
    return *this;
}

template<typename T>
T& Array<T>::operator[](int index) {
    return elements_[index];
}

template<typename T>
int Array<T>::size() const {
    return length_;
}

template<typename T>
typename Array<T>::iterator Array<T>::begin() {
    return iterator(*this, 0);
}

template<typename T>
typename Array<T>::iterator Array<T>::end() {
    return iterator(*this, size());
};

template<typename T>
void Array<T>::internal_swap(Array& other){
    T* oelements = other.elements_;
    other.elements_ = this->elements_;
    this->elements_ = oelements;
}

Array<int> get_values(int x);

int main(int argc, const char *argv[]) {

    Array<int> a = get_values(2);

    for (Array<int>::iterator i = a.begin(); i != a.end(); ++i) {
        std::cout << *i << std::endl;
    }

    return 0;
}

Array<int> get_values(int x) { 
    Array<int> a(10);


    if(x == 1) return a;


    for (int i = 0; i <= 9; i++) {
        a[i] = 1 + i;
    }

    return a;
}
Cockalorum answered 26/3, 2012 at 1:42 Comment(5)
What are your compiler flags for both MSVC and Clang?Mortensen
All defaults with XCode 4.3 and VS 2010.Cockalorum
Yeah, but, the default in VS is debug mode, aka no optimizations... So let's word it a bit different: Did you compile both in release mode / with optimizations enabled?Mortensen
@Xeo: Compiling with /O2 (generate fast code) and /GL (whole program optimization) still outputs "move ctor"...Pagel
Note that if you comment out the line if(x == 1) return a; in your get_values() function body, NRVO is applied. It seems like the if statement prevents NRVO...Pagel
B
8

Copy elision is one of those rare optimizations where the standard allows different observable behavior (it doesn't fall under the as-if rule), yet isn't undefined behavior.

Whether any copy or move constructor is called or elided in this context is unspecified, and different compilers can behave differently and both be correct.

Balneology answered 26/3, 2012 at 2:53 Comment(5)
I thought move ctor is preferred?Cockalorum
@Blair: Oh no, constructing the object in the right place to begin with is definitely preferred to moving it and destroying the old one.Balneology
So your saying Move is used only if RVO / NRVO cant be used? i.e. last resort?Cockalorum
Yes, that's why the new standard specifically allows move elision under the same conditions when copy elision was formerly allowed.Balneology
Thank you very much that is awesome. Totally explains the behaviour then. Plus makes sense over why NRVO / RVO is preferred over move semantics.Cockalorum

© 2022 - 2024 — McMap. All rights reserved.