如何实现具有 const 正确性的 Vector,禁止此类 Vector 的普通迭代器更改迭代器地址处的值

Fre*_*gal 2 c++ templates iterator vector const-correctness

背景:

\n

我正在编写自己的 vector.h 是为了好玩(请不要评判)。我试图保持 const 正确,因此我为我的 Vector::iterator 类实现了const .begin() 函数和非 const .begin() 函数。我的 CMakeLists.txt 文件需要 C++23

\n

问题:

\n

不幸的是,在我的 UnitTest 中,我能够声明一个const向量,然后声明一个非 const迭代器,并使用迭代器成功更改 const 向量的值。这显然不是 const Vector 的重点。

\n

如果您有时间,请查看我的实现,看看我忽略了什么。完整代码可以在我的 github 上找到。片段如下:

\n

我的代码:

\n

测试是否存在不需要的行为

\n
TEST(testConstBegin) {\n    const Vector<int> c_myVec = {0,1,2,4};\n    Vector<int>::iterator it = c_myVec.begin(); // NOTE not const.\n    \n    // c_myVec[0] = 4;            // fails as expected\n    *it = 4;                      // should fail but does not. THIS IS WHY I WROTE THIS QUESTION.\n    CHECK_EQUAL(c_myVec[0], 0);   // This line should never run.\n}\n
Run Code Online (Sandbox Code Playgroud)\n

迭代器分配因子

\n
   template <typename T>\n    typename Vector<T>::iterator::iterator_reference\n    Vector<T>::iterator::operator=(\n           typename Vector<T>::iterator::const_iterator_reference other) {\n        if (this != &other) \n            iter_ = other.iter_;\n        \n        return *this;\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

常量 .begin()

\n
 template <typename T>\n    typename Vector<T>::const_iterator \n    Vector<T>::cBegin() const {\n        return typename Vector<T>::iterator::const_iterator(array_);\n    }\n    \n    // issue #001\n    template <typename T>\n    typename Vector<T>::const_iterator \n    Vector<T>::begin() const {\n        return typename Vector<T>::iterator::const_iterator(array_);\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

。开始()

\n
   template <typename T>\n    typename Vector<T>::iterator\n    Vector<T>::begin() {\n        return iterator(array_);\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

常量运算符*()

\n
   // const operator*\n    template <typename T>\n    typename Vector<T>::iterator::const_reference\n    Vector<T>::iterator::operator*() const {\n        return *iter_;\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

非常量运算符*()

\n
   // operator*\n    template <typename T>\n    typename Vector<T>::iterator::reference\n    Vector<T>::iterator::operator*() {\n        return *iter_;\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

stl_vector.h 实现

\n
\n      /**\n       *  Returns a read-only (constant) iterator that points to the\n       *  first element in the %vector.  Iteration is done in ordinary\n       *  element order.\n       */\n      const_iterator\n      begin() const _GLIBCXX_NOEXCEPT\n      { return const_iterator(this->_M_impl._M_start); }\n\n\n
Run Code Online (Sandbox Code Playgroud)\n
\n

我试图创建的 stl_vector.h 行为

\n
testVectorFillCtor.cpp:32:50: error: conversion from \xe2\x80\x98__normal_iterator<const int*,[...]>\xe2\x80\x99 to non-scalar type \xe2\x80\x98__normal_iterator<int*,[...]>\xe2\x80\x99 requested\n   32 |     std::vector<int>::iterator it = c_myVec.begin();\n      |                                     ~~~~~~~~~~~~~^~\n
Run Code Online (Sandbox Code Playgroud)\n

alf*_*lfC 10

实现容器并不容易,但如果你这样做,我绝对尊重你的目标是 const 正确。事实上,如果你不打算 100%“const-正确”,就不要做这种事情。

我在你的代码中看到的最初的缺陷是这里的这个,

https://github.com/PIesPnuema/stl_implementation_practice/blob/main/include/vector.h#L38

        typedef const iterator           const_iterator;
Run Code Online (Sandbox Code Playgroud)

const_iterator需要作为与 不同的类来实现iterator,因此它可以在取消引用时返回const引用。

无论如何, aconst_iterator与迭代器 无关const。想一想,a 可以const_itertor变异吗(例如,通过增量)?大概是的,所以肯定不一样iterator constdouble const*这与和之间的差异相同double* const(使用EastConst就不会出现这种混乱)。

换句话说,const_iteratoriterator基本上是独立的类型。对于向量的情况,它们可以简单地分别定义为double const*double*;但这将隐藏实现迭代器的一般复杂性。所以,我假设我们希望迭代器是类。

我已经实现了类似矢量的容器;不幸的是,没有其他办法。

您可以通过在和中参数化单个公共basic_iterator<Ref>基础来节省一些代码重复,但这是一个不同的故事。T&T const&

这是我为说明这一点而想出的最小代码:

        typedef const iterator           const_iterator;
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/z/vGT8j3WrG

有关完整的示例,请查看我的容器数组库,按照这一行的白兔,https://gitlab.com/correaa/boost-multi/-/blob/master/include/multi/array_ref.hpp#L1267


为了避免一些代码重复,您可以使用一些模板技巧:

class Vec {
    double value_ = 5;

    template<template<typename> class Modifier>
    struct iterator_base {
        Modifier<double>::type* ptr_;
        auto operator*() const -> auto& {return *ptr_;}
    };

public:
    using       iterator = iterator_base<std::type_identity>;
    using const_iterator = iterator_base<std::add_const    >;

    auto begin()       ->       iterator {return {&value_};}
    auto begin() const -> const_iterator {return {&value_};}
};
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/z/81GK69G7W


如果你想完全消除代码重复并自动从 转换iteratorcons_iterator,你可以这样做,尽管还不清楚这是否是真正的收获。

class Vec {
    std::unique_ptr<double> base_ = std::make_unique<double>(5.0);

    template<template<typename> class ConstQ>
    class iterator_base {
        ConstQ<double>::type* ptr_;
        friend class iterator_base<std::add_const>;

    public:
        explicit iterator_base(ConstQ<double>::type* ptr) : ptr_{ptr} {}
        iterator_base(iterator_base<std::type_identity> const& other) : ptr_{other.ptr_} {}
        auto operator*() const -> auto& {return *ptr_;}
    };

public:
    using       iterator = iterator_base<std::type_identity>;
    using const_iterator = iterator_base<std::add_const    >;

private:
    auto begin_aux() const {return iterator(base_.get());}

public:
    auto begin()       ->       iterator {return begin_aux();}
    auto begin() const -> const_iterator {return begin_aux();}
};
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/z/PEqqdTefe

通过使用有问题的继承扩展技术,可以消除虚假基类和奇怪的构造函数;稍后在迭代器类上实现突变成员时必须小心。请注意,如果在实现时小心谨慎,常量性不会从设计中逃脱:

    class const_iterator {
        const_iterator(double* ptr) : ptr_{ptr} {}
        double* ptr_;
        friend class Vec;

    public:
        auto operator*() const -> double const& {return *ptr_;}
    };
    struct iterator : const_iterator {
        auto operator*() const -> double      & {return *ptr_;}
    };
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/z/Ye1fj5faE