Search code examples
c++visual-c++clangvisual-c++-2010move-semantics

Move semantics in MS C++ vs Clang


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

Solution

  • 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.