Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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;
}
like image 253
Blair Davidson Avatar asked Mar 26 '12 01:03

Blair Davidson


1 Answers

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.

like image 142
Ben Voigt Avatar answered Sep 28 '22 07:09

Ben Voigt