Fre*_*gal 2 c++ templates iterator vector const-correctness
我正在编写自己的 vector.h 是为了好玩(请不要评判)。我试图保持 const 正确,因此我为我的 Vector::iterator 类实现了const .begin() 函数和非 const .begin() 函数。我的 CMakeLists.txt 文件需要 C++23
\n不幸的是,在我的 UnitTest 中,我能够声明一个const向量,然后声明一个非 const迭代器,并使用迭代器成功更改 const 向量的值。这显然不是 const Vector 的重点。
\n如果您有时间,请查看我的实现,看看我忽略了什么。完整代码可以在我的 github 上找到。片段如下:
\nTEST(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 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 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 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 // 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 // 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\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)\ntestVectorFillCtor.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 const
。double const*
这与和之间的差异相同double* const
。(使用EastConst就不会出现这种混乱)。
换句话说,const_iterator
和iterator
基本上是独立的类型。对于向量的情况,它们可以简单地分别定义为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
如果你想完全消除代码重复并自动从 转换iterator
为cons_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