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;
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With