MS C++与Clang中的移动语义

6
在我创建的数组类型上进行一些移动语义实验后,我想知道为什么微软的C++编译器在按值从一个方法返回时调用移动构造函数,而Clang编译器则完全省略了复制?这是Clang的正确行为还是不正确的行为?或者是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;
}

你使用的 MSVC 和 Clang 的编译器标志是什么? - Xeo
所有的默认设置都是 XCode 4.3 和 VS 2010。 - Blair Davidson
2
是的,但是,VS的默认模式是调试模式,也就是没有优化... 所以我们来说得再清楚一些:你是否在释放模式下编译并启用了优化? - Xeo
@Xeo:使用/O2(生成快速代码)和/GL(整个程序优化)编译仍然输出“move ctor”... - user1149224
请注意,如果在您的 get_values() 函数主体中注释掉 if(x == 1) return a; 行,将应用 NRVO。似乎这个 if 语句阻止了 NRVO... - user1149224
1个回答

8

复制省略是一种罕见的优化,标准允许不同的可观察行为(不属于 as-if 规则),但它并非未定义的行为。

在此情况下是否调用或省略任何复制或移动构造函数是不确定的,不同的编译器可能行为不同但都是正确的。


我认为移动构造函数更受欢迎? - Blair Davidson
3
@Blair:哦不,最好是在正确的位置构建对象,而不是移动它并摧毁旧的对象。 - Ben Voigt
那么你的意思是只有在无法使用RVO / NRVO时才会使用Move?也就是说,这是最后的选择? - Blair Davidson
是的,这就是为什么新标准特别允许在以前允许复制省略的相同条件下进行移动省略。 - Ben Voigt
非常感谢,太棒了。完全解释了这种行为。此外,这也说明了为什么 NRVO / RVO 优于移动语义。 - Blair Davidson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接