【问题标题】:Implementing push_back(T&& c) in custom Vector<T> class在自定义 Vector<T> 类中实现 push_back(T&& c)
【发布时间】:2020-04-07 15:44:27
【问题描述】:

作为 Uni 任务的一部分,我必须实现 stl::vector 类的部分克隆。我已经管理了几乎所有的功能,但我对如何执行 push_back(T&& c) 感到困惑。我们不允许像 STL 本身使用的那样实现 emplace。用于测试 push_backs 实现的对象检查在其上调用了哪些构造函数和运算符。对于这个,只允许调用移动构造函数。由于整个电晕hoohaa,很难从老师/其他学生那里获得任何帮助。

这是我目前所拥有的:

template <typename T>
void Vector<T>::push_back(T&& c)
{
  // code that resizes the vector if needed. Checked to make sure it doesn't invoke anything.
  T* a = new T(std::move(c)); // Invokes the Move Constructor, which is fine.
  auto b = (_vector_data+_vector_size); // Destination
  //std::move(); Move invokes MA
  //std::swap(b, a); // Doesn't work, swaps but the value of a ends up somewhere else, I use this method in push_back(const T& c) successfully.
  //std::copy(); // Copy invokes CA
  ++_vector_size;
}

_vector_data 是 T*,_vector_size 是 size_t。

为了帮助说明为什么我认为这些模式是另一个 push_back:

template <typename T>
void Vector<T>::push_back(const T& c)
{
  // code that resizes the vector if needed.
  T* a = new T(c); // Invokes CC.
  auto b = (_vector_data+_vector_size); // Destination
  std::swap(b, a);
  ++_vector_size;
}

我完全不知道该怎么办。我仔细阅读了源材料、书籍和谷歌,但我想我可能太愚蠢了,没有意识到我做错了什么或去哪里看:P Halp?

我也看过这个: Vector push_back move implementation 这看起来像是我的第一次尝试。它没有通过测试,因为最后一行调用了复制赋值运算符,导致断言失败。

编辑:

澄清一下。测试程序的部分运行如下:

struct C {
    static std::string usedConstr;
    void Test() {}
    C() {
        usedConstr += "DC";
    }
    C(const C& c) {
        usedConstr += "CC";
    }
    C(C&& c) {
        usedConstr += "MC";
    }
    C& operator=(const C& c) {
        usedConstr += "CA";
        return *this;
    }
    C& operator=(C&& c) {
        usedConstr += "MA";
        return *this;
    }
};

std::string C::usedConstr{};

//Test push_back&&
void TestMove() {
    Vector<C> a;
    C c;
    assert(C::usedConstr == "DC");
    a.reserve(4);
    C::usedConstr = "";
    a.push_back(c);
    assert(C::usedConstr == "CC");
    C::usedConstr = "";
    a.push_back(std::move(c));
    assert(C::usedConstr == "MC");
}

最后一个失败的断言在哪里。仅调用移动构造函数是无法到达那里的。

【问题讨论】:

  • 如果它是一个向量,你应该有一个像缓冲区这样的东西来存储所有元素,所以不会有这样的T* a = new T(c),只有*b = c*b = std::move(c)
  • 从显示的代码中,我怀疑这个“Vector”是作为指向单个元素的指针数组实现的?无论如何,您的代码中的问题可能不在此处显示的范围内。
  • @fas 这会导致移动分配运算符检查对存储的对象触发,从而导致断言失败。
  • @DrewDormann 其余代码不涉及如何将事物插入到数组中?
  • 您可能想看看std::aligned_storage。该页面甚至给出了如何将其用于类矢量类的示例。

标签: c++ move push-back


【解决方案1】:

std::vector 通常通过operator new 分配内存或传递给向量类的一些分配器并在该存储中构造新对象来实现。这里有一些关于不使用分配器直接调用operator new 的示例。 (真正的std::vector总是使用分配器,默认std::allocator。):

_vector_data = static_cast<T*>(::operator new(sizeof(T)*new_capacity));

这会分配内存,但不会显式创建任何对象。 (请注意,这只是非过度对齐类型正确对齐的内存,但这应该是可以接受的限制。)

然后要将新对象放置到该存储中,您将使用 非分配放置新

template <typename T>
void Vector<T>::push_back(T&& c)
{
    ::new(static_cast<void*>(_vector_data+_vector_size)) T(std::move(c));
    ++_vector_size;
}

(只需要先验证容量是否足够)

如果您不关心与 operator new 类重载相关的一些极端情况,您可以使用

new(_vector_data+_vector_size) T(std::move(c));

而不是

::new(static_cast<void*>(_vector_data+_vector_size)) T(std::move(c));

operator new

而不是

::operator new

为了删除元素,您可以使用例如显式调用它们的析构函数

(_vector_data+_vector_size)->~T();

您可以通过调用与您用于operator new 的表单匹配的operator delete 来释放内存。

这在 C++20 之前在技术上具有未定义的行为,因为 _vector_data+_vector_size 中的指针运算仅在 _vector_data 指向数组的元素时才允许,但我们从未创建过数组。从 C++20 开始,这个数组是隐式创建的

没有办法实现std::vector,它在指向其元素的指针上具有正确的数组语义,满足您在 C++20 之前给出的要求,而不依赖于根据标准未定义行为的语义,尽管这未定义的行为更多是技术性的,在实践中可能永远不会引起问题。

【讨论】:

  • 谢谢。我不完全确定这里发生了什么,但它确实有效并通过了所有测试。我想我还有很多学习要做。我向你脱帽致敬,伙计。它是否在数组的分配槽中分配了一个新的 void 指针,然后将 T&& 的内容移到那里?
  • 你不能使用new来实现vector:它没有提供像reserve()pop_back()这样的函数所需要的手动构造和销毁的机会。跨度>
  • @Daniel 使用operator new 的第一行分配了一个足够大的内存块来保存T 类型的_vector_capacity 对象,但不创建任何这些对象。 new(_vector_data+_vector_size) T(std::move(c)); 然后直接在该存储的_vector_data+_vector_size 位置创建一个新的T 对象。这称为 *placement-new。请阅读它的使用方式以及它与常规new 的不同之处。 (重要的区别!)
  • @passing_through 这是给我的吗?这就是我使用非分配放置新表单的原因,它确实允许就地构造和销毁元素。
  • @walnut 你是对的,对不起。希望我至少提供了一些关于为其他读者分配新的信息。
【解决方案2】:

您似乎忘记在此版本的push_back 中调整矢量的大小。

template <typename T>
void Vector<T>::push_back(T&& c)
{
  T* a = new T(std::move(c)); // Invokes the Move Constructor, which is fine.
  auto b = (_vector_data+_vector_size); // Destination
  std::move(b,a);
  ++_vector_size;  // WHOOPS
}

因为我在这里,所以没有必要将指针分配给a。那是错误的目的地。

void Vector<T>::push_back(T&& c)
{
  auto b = (_vector_data+_vector_size); // Correct destination
  b = new T(std::move(c)); // Invokes the Move Constructor, which is fine.
  ++_vector_size;
}

【讨论】:

  • 谢谢德鲁!我知道我忘了复制那一行。然而,这不是问题。您在那里的更新代码不起作用,它会相应地更新 b 但不是基础向量。 IE。 ``` Vector vecBar("Bar"); vecBar.push_back('a');断言(vecBar ==“巴拉”); // 失败 ```
猜你喜欢
  • 2017-09-15
  • 1970-01-01
  • 2018-04-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多