在自定义 Vector<T> class 中实现 push_back(T&& c)

Implementing push_back(T&& c) in custom Vector<T> class

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

这是我目前拥有的:

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;
}

我完全不知所措。我仔细阅读了来源 material、这本书和 google,但我想我可能太愚蠢了,没有意识到我做错了什么或去哪里找 :P Halp?

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

编辑:

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

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");
}

最后一个失败的断言在哪里。我无法通过仅调用 Move Constructor 到达那里。

您似乎忘记了在此版本的 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;
}

std::vector 通常是通过 operator new 或传递给向量 class 的某个分配器分配内存并在该存储中构造新对象来实现的。这里有一些不使用分配器直接调用 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 class 重载相关的一些边缘情况,您可以使用

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_data+_vector_size 中的指针算法,但我们从未创建数组。自 C++20 以来,此数组是 隐式创建的

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