为什么要复制而不是移动我的数据元素?

问题描述 投票:1回答:5

我正在执行一些有关移动语义的测试,我的类行为对我来说很奇怪。

Given模拟类VecOfInt

class VecOfInt {
public:
    VecOfInt(size_t num) : m_size(num), m_data(new int[m_size]) {}
    ~VecOfInt() { delete[] m_data; }
    VecOfInt(VecOfInt const& other) : m_size(other.m_size),  m_data(new int[m_size]) {
        std::cout << "copy..." <<std::endl;
        std::copy(other.m_data, other.m_data + m_size, m_data);
    }
    VecOfInt(VecOfInt&& other) : m_size(other.m_size) {
        std::cout << "move..." << std::endl;
        m_data = other.m_data;
        other.m_data = nullptr;
    }
    VecOfInt& operator=(VecOfInt const& other) {
        std::cout << "copy assignment..." << std::endl;
        m_size = other.m_size;
        delete m_data;
        m_data = nullptr;
        m_data = new int[m_size];
        m_data = other.m_data;
        return *this;
    }
    VecOfInt& operator=(VecOfInt&& other) {
        std::cout << "move assignment..." << std::endl;
        m_size = other.m_size;
        m_data = other.m_data;
        other.m_data = nullptr;
        return *this;
    }
private:
    size_t m_size;
    int* m_data;
};
  1. OK CASE

    [When我就地插入一个值:

    int main() {
        std::vector<VecOfInt> v;
        v.push_back(10);
        return 0;
    }
    

    然后它为我提供以下输出(我认为很好)

    move...

  2. 奇怪的情况

    When我就地插入了三个不同的值:

    int main() {
        std::vector<VecOfInt> v;
        v.push_back(10);
        v.push_back(20);
        v.push_back(30);
        return 0;
    }
    

    然后输出将调用复制构造函数3次:

    move... move... copy... move... copy... copy...

我在这里想念的是什么?

c++ c++11 stdvector move-semantics move-constructor
5个回答
6
投票

MoC构造和移动分配在重新分配时不被std::vector使用,除非它们为noexcept或没有复制替代品。这是添加了noexcept的示例:

class VecOfInt {
public:
    VecOfInt(size_t num) : m_size(num), m_data(new int[m_size]) {}
    ~VecOfInt() { delete[] m_data; }
    VecOfInt(VecOfInt const& other) : m_size(other.m_size),  m_data(new int[m_size]) {
        std::cout << "copy..." <<std::endl;
        std::copy(other.m_data, other.m_data + m_size, m_data);
    }
    VecOfInt(VecOfInt&& other) noexcept : m_size(other.m_size) {
        std::cout << "move..." << std::endl;
        m_data = other.m_data;
        other.m_data = nullptr;
    }
    VecOfInt& operator=(VecOfInt const& other) {
        std::cout << "copy assignment..." << std::endl;
        m_size = other.m_size;
        delete m_data;
        m_data = nullptr;
        m_data = new int[m_size];
        m_data = other.m_data;
        return *this;
    }
    VecOfInt& operator=(VecOfInt&& other) noexcept {
        std::cout << "move assignment..." << std::endl;
        m_size = other.m_size;
        m_data = other.m_data;
        other.m_data = nullptr;
        return *this;
    }
private:
    size_t m_size;
    int* m_data;
};

实时live example输出:

move...
move...
move...
move...
move...
move...

这样做是为了保持异常安全。当std::vector的大小调整失败时,它将尝试使向量保持原样。但是,如果移动操作在重新分配过程中进行,则没有安全的方法来撤消已经成功进行的移动。他们也可能会抛出。最安全的解决方案是如果可能移​​动会进行复制。


2
投票

std::vector为它的元素分配一块连续的内存。如果分配的内存不足以存储新元素,则会分配一个新块,并将所有当前元素从旧块复制到新块。

您可以在添加新元素之前使用std::vector::reserve()来预先确定std::vector::reserve()存储器的容量。

尝试以下操作:

std::vector

您将获得:

int main() {
    std::vector<VecOfInt> v;
    v.reserve(3);
    v.push_back(10);
    v.push_back(20);
    v.push_back(30);
    return 0;
}

但是,即使在重新分配时也要调用move构造函数,您应该使其像move... move... move... 一样:

noexcept

2
投票

tl; dr:如果您的move构造函数不是VecOfInt(VecOfInt&& other) noexcept {...} ,则std::vector将复制而不是移动。

1。与成员和动态分配无关

问题不在于您对foo字段所做的事情。因此,您的来源可能只是:

noexcept

并且您仍然得到相同的行为:class foo { public: foo(size_t num) {} ~foo() = default foo(foo const& other) { std::cout << "copy..." <<std::endl; } foo(foo&& other) { std::cout << "move..." << std::endl; } foo& operator=(foo const& other) { std::cout << "copy assignment..." << std::endl; return *this; } foo& operator=(foo&& other) { std::cout << "move assignment..." << std::endl; return *this; } };

2。您的举动确实让人分心]

现在,try it首先将构造一个元素-push_back();然后确保向量中有空间;然后将其foo放到其位置。因此,您的3个举动就是这种情况。让我们尝试使用std::move()代替它在原处构造矢量元素:

emplace_back()

这给我们:

#include <vector>
#include <iostream>

struct foo { // same as above */ };

int main() {
    std::vector<foo> v;
    v.emplace_back(10);
    v.emplace_back(20);
    v.emplace_back(30);
    return 0;
}

copy copy copy 。因此,这些举动实际上只是分散注意力。

3。副本归因于向量本身的大小调整]

您的try it随着插入元素而逐渐增长-需要移动或复制构造。有关详细信息,请参见std::vector

4。真正的问题是例外

看到此问题:

@NutCracker's post

[How to enforce move semantics when a vector grows?不知道调整大小时可以安全地移动元素-其中“安全”是指“无例外”,因此它可以复制。

5。 “但是我的复制ctor也可以抛出异常!”

我想,基本原理是,如果在复制较小的缓冲区时遇到异常-您仍然没有碰过它,那么至少您原始的,未调整大小的向量是有效的并且可以使用。如果您启动moving元素并遇到异常-那么您仍然没有该元素的有效副本,更不用说有效的较小矢量了。]


1
投票

您的移动构造函数没有指定符std::vector


0
投票

VecOfInt(VecOfInt&& other) noexcept : m_size(other.m_size) { std::cout << "move..." << std::endl; m_data = other.m_data; other.m_data = nullptr; } 装饰器添加到您的move构造函数和move赋值运算符:

© www.soinside.com 2019 - 2024. All rights reserved.