首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >当不存在xvalue时,移动语义如何应用于以下片段?

当不存在xvalue时,移动语义如何应用于以下片段?
EN

Stack Overflow用户
提问于 2020-02-04 01:06:42
回答 2查看 66关注 0票数 1

我无意中发现了下列条款,并且不理解C++98和C++11之间的性能差异,正如作者所说,这归因于移动语义。

代码语言:javascript
复制
#include <vector>

using namespace std;

int main() {
    vector<vector<int> > V;

    for(int k = 0; k < 100000; ++k) {
        vector<int> x(1000);
        V.push_back(x);
    }

    return 0;
}

据我所知,V.push_back(x)不调用任何移动语义。我相信x是一个lvalue,这个片段在C++98和C++11中都调用相同的vector::push_back(const T&)

代码在任何一个版本上都编译相同:https://godbolt.org/z/q3Lzae

作者的陈述是否不正确,或者编译器是否聪明到足以意识到x即将被销毁?

如果作者不正确,那么C++11中还有什么东西可以“不改变一行代码”地提高性能呢?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-02-04 02:07:14

你是对的,x是不会被移动的。获得性能的移动操作与已经在k中的其他V向量有关。

随着向量的增长(除非reserve具有足够的大小),它有时需要重新分配以获得更大的内存块,因为它的元素必须位于连续内存中。并不是每个push_back都会发生这种情况,但在本例中肯定会发生这种情况。假设push_back和其他函数使用了一些私有函数grow_capacity,它获得了足够的内存,然后在该内存中的向量中创建对象。

在C++03中,为任意模板参数T在新内存中创建对象的唯一合理方法是使用T的复制构造函数。

代码语言:javascript
复制
// C++03 implementation?
template <typename T, typename Alloc>
std::vector<T, Alloc>::grow_capacity(::std::size_t new_capacity)
{
    T* new_data = get_allocator().allocate(new_capacity);
    T* new_end = new_data;
    try {
        for (const_iterator iter = begin(); iter != end(); ++iter) {
            ::new(static_cast<void*>(new_end)) T(*iter); // T copy ctor!
            ++new_end;
        }
    } catch (...) {
        while (new_end != new_data) (--new_end)->~T();
        get_allocator().deallocate(new_data, new_capacity);
        throw;
    }

    // Clean up old objects and memory.
    for (const_reverse_iterator riter = rbegin(); riter != rend(); ++riter)
        riter->~T();
    get_allocator().deallocate(_data, _capacity);

    // Assign private members.
    _data = new_data;
    _capacity = new_capacity;
}

在C++11和以后的版本中,当std::vector<T>需要重新分配到更大的容量时,允许移动它的T元素,而不是复制它们,如果它可以这样做的话,而不破坏强异常保证。这要求声明移动构造函数以避免引发任何异常。但是,如果移动构造函数可能抛出,则需要以旧的方式复制元素,以确保如果发生这种情况,向量将保持一致状态。

代码语言:javascript
复制
// C++17 implementation?
template <typename T, typename Alloc>
std::vector<T, Alloc>::grow_capacity(::std::size_t new_capacity)
{
    T* new_data = get_allocator().allocate(new_capacity);

    if constexpr (::std::is_nothrow_move_constructible_v<T>) {
        ::std::uninitialized_move(begin(), end(), new_data);   // T move ctor!
    } else {
        T* new_end = new_data;
        try {
            for (const T& old_obj : *this) {
                ::new(static_cast<void*>(new_end)) T(old_obj); // T copy ctor!
                ++new_end;
            }
        } catch (...) {
            while (new_end != new_data) (--new_end)->~T();
            get_allocator().deallocate(new_data, new_capacity);
            throw;
        }
    }

    for (const_reverse_iterator riter = rbegin(); riter != rend(); ++riter)
        riter->~T();
    get_allocator().deallocate(_data, _capacity);

    // Assign private members.
    _data = new_data;
    _capacity = new_capacity;
}

因此,在具有std::vector<std::vector<int> >类型的容器中,Tstd::vector<int>。以C++03方式增加容量有时需要大量的复制构造函数,然后是std::vector<int>的析构函数。每个复制构造函数分配一些内存并复制1000个int值,每个析构函数释放一些内存,因此这将真正地加起来。但是,对于C++11 std::vector,由于元素类型std::vector<int>确实有noexcept移动构造函数,所以std::vector<std::vector<int>>容器可以只使用该移动构造函数,这只是标量成员的几个交换,并且还会导致迁移的析构函数--从旧对象到旧对象--什么都不做。

票数 4
EN

Stack Overflow用户

发布于 2020-02-04 07:15:50

在这个例子中,x将在push_back调用中超出作用域(它的生命周期结束,并且没有后续使用),所以编译器可能会将它视为xvalue并移出它。这不是编译器需要进行移动优化的情况之一,所以它可能不会,但是如果启用了优化,那么任何体面的编译器都会这样做( gcc和clang都会在这里使用move )。

票数 -1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60049233

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档