【问题标题】:Different implementations of begin() and end() of a container容器的 begin() 和 end() 的不同实现
【发布时间】:2020-10-13 22:31:27
【问题描述】:

我正在练习实现容器。我的目标是定义迭代器 begin()end() 这样我就可以有for(auto x : v) 形式的循环。我的容器如下所示:

class Vector{
public:
    Vector(initializer_list<double> numbers){
        sz = numbers.size();
        elem = new double[sz];
        int i = 0;
        for (auto it = numbers.begin(); it!=numbers.end(); ++it)
            elem[i++] = *it;
    }
    ~Vector(){delete [] elem;}
    double* begin();
    double* end();
private:
    double* elem;
    int sz;
    
};

选项 1

这就是我定义迭代器的方式(它们在我的测试用例中工作得很好)

double* Vector::begin(){
    return elem;
}

double* Vector::end(){
    return &elem[sz];
}

选项 2

这就是它们在C++之旅中的定义

double* Vector::begin(){
    return &elem[0];
}

double* Vector::end(){
    return &elem[0]+sz;
}

我的问题

据我所知,这两个选项都可以正常工作(假设容器非空)。与选项 1 相比,选项 2 是否有任何优势(反之亦然)?我很感激任何建议。

【问题讨论】:

  • double* Vector::end() { return elem + sz; }?
  • 附带说明,您的Vector 类违反了Rule of 3/5/0,因为它没有实现/删除复制构造函数和复制赋值运算符,以及移动构造函数和移动赋值运算符。除了作为一种学习体验之外,实现自定义 Vector 类而不是使用 std::vector 并没有真正的好处。

标签: c++ iterator containers


【解决方案1】:

虽然&amp;elem[sz]&amp;elem[0]+sz 最终会在大多数/所有系统上为您提供相同的结果,但第一个实际上是未定义的行为。当你这样做时

&elem[sz]

你确实在做

&*(elem +sz)

*,即取消引用,是一个不存在的元素。根据 C++ 标准,这是未定义的行为。

&elem[0]+sz

你得到一个指向第一个元素的指针,如果指针指向一个实际的数组,那么它是合法的,然后你将它推进到最后一个。这是获取结束迭代器的合法且正确的方法,前提是elem 不为空且指向有效数组。


另一种方法是使用

return elem + sz;

因为它不需要任何解除引用。

【讨论】:

  • "使用&amp;elem[0]+sz,您将获得指向第一个合法元素的指针" - 前提是elem 不为空且sz 不为0,否则@ 987654335@ aka *elem 将取消引用一个不存在的元素。 return elem + sz; 在这种情况下是首选,根本不会发生解除引用。
  • 我可能对此有误,但我似乎记得在某处读过规范明确指出&amp;* 是无操作的。虽然也许那是 C 而不是 C++?
  • @templatetypedef This 是涵盖*&amp; 的部分,就这个问题而言,它在哪里没有说做&amp;* 是一个非操作,所以充其量它是编译器优化。
  • 如果endref 从未被取消引用,而只是获取了它的地址,那么还有一个discussionfollow-up question 关于double&amp; endref = &amp;elem[sz]; 是否实际上是UB。都没有得到明确的答案。
  • @TedLyngmo 很有趣。我越看文本,它也越让我困惑。 * 说它需要是指向对象类型的指针,而不是对象。我能找到的唯一另一件事是 否则,如果操作数是来自 [expr.unary.op] 广告的 T 类型的左值,我不确定 是否是 T 类型的左值 适用于假设的结束对象。
【解决方案2】:

这两个选项都有效,如果编译器没有为每个版本生成等效代码,我会感到惊讶。

我实际上更喜欢你对begin 的实现,因为如果elem 是一个指针,那么与elem 相比,&amp;elem[0] 是多余的。

另一种选择:对于end,您可以执行类似的操作

return begin() + size();

假设你有一个size() 成员函数。这不需要获取任何地址,而是更直接地说“size() 的位置从begin() 指向的位置向下移动。”但那只是我的个人意见。 :-)

希望这会有所帮助!

【讨论】:

  • end()return elem + sz; 内部会更干净,并避免任何不必要的函数调用。
  • 多年来我得到的建议是尽可能在类实现中使用公共接口,其想法是让类在未来更容易编辑/修改并阐明意图。 (我想我最初是在“Effective C++”中看到的。)但这可能不像我想象的那么标准?
猜你喜欢
  • 1970-01-01
  • 2014-03-18
  • 2013-09-06
  • 2019-05-18
  • 2020-10-14
  • 2021-03-30
相关资源
最近更新 更多