基于 C++17 中的范围,用于自定义容器或具有不同开始/结束类型的通用类

Posted

技术标签:

【中文标题】基于 C++17 中的范围,用于自定义容器或具有不同开始/结束类型的通用类【英文标题】:Range based for in C++17 for custom container or general classes with different begin/end types 【发布时间】:2019-05-23 18:37:00 【问题描述】:

我正在使用 g++ v8.2 和 -std=c++2a -Wall- -Wextra -pedantic 在 Raspberry Pi 3B+ 上运行。我正在尝试更好地了解我的自定义容器的基于循环的范围。

您在下面看到一个类,它实现了一个基于范围的自定义类。那工作正常。然而。我对我的工作实施有一些额外的具体问题。

我已经检查过this 和其他人。但这些都没有回答我的问题。

代码如下:

#include <iostream>
struct LinkedList  // Simplest linked list

    int k;  
    LinkedList *next;
;
// For test pruposes: Build manually linked list as globals
LinkedList aa33,nullptr;  LinkedList aa22,&aa3;  LinkedList aa11,&aa2;

class Example

    public:
        Example    &begin       (void)        linkedList = linkedListStartAddress; return *this;
        int         end         (void)        return 0;   
        LinkedList &operator *  (void)        return *linkedList; 
        void        operator ++ (void)        linkedList = linkedList->next; 
        bool        operator != (const int&)  return (linkedList != nullptr);
    protected:
        LinkedList *linkedListStartAddress &aa1; // Global initialisation for test purposes
        LinkedList *linkedList&aa1;              // Global initialisation for test purposes
 ;

int main(void)

    Example example;
    for (auto l : example)
    
        std::cout << l.k << '\n';
    
    return 0;

好的,这行得通。

循环的一般定义是:


    auto && __range = range_expression ;
    auto __begin = begin_expr;
    auto __end = end_expr
    for (;__begin != __end; ++__begin) 
        range_declaration = *__begin;
        loop_statement
    

这很难理解。 __range 和 range_expression 都不会在后面使用。在这种情况下,“汽车”对我来说也很困难。我看不到类型。现在我的假设和问题。

    示例类同时是容器和迭代器。这是一个正确或常见的用例吗? 显然“begin”的返回类型必须是“range_expression”的类型,反之亦然。正确吗? 'begin' 的返回类型必须是类类型,否则将不会调用运算符(++、*、!=)。正确吗? 调用“开始”函数只是为了进行初始化。并返回对该类的引用。它不一定与底层容器有关系。正确吗? 'end' 函数(我检查过:它在 'begin' 之后立即调用。然后再也不会调用)没有任何意义 (c++17)。它只是在那里定义(及其返回值)'operator !='右侧的类型。正确吗? 如何将“运算符!=”的类型推断为“const 'ReturnTypeOfEnd' &”。我尝试使用 decltype,但失败了。 显然,'operator*' 的返回类型定义了 range_declaration 的类型。在我的情况下,'*__begin' 的类型与 '__begin' 完全不同。不是地址什么的。好吗?

如果您能对此有所了解,我会很高兴。 . .

【问题讨论】:

auto &amp;&amp; __range = range_expression 仅表示对范围表达式进行评估。那是“for (x:y)”的“y”部分。您可以将begin_exprend_expr 视为基本上std::begin(__range)std::end(__range)。这几乎就是胶囊摘要,不超过 600 个字符。 重复的***.com/q/8164567/2466431? How to make my custom type to work with "range-based for loops"?的可能重复 我在提问之前阅读了这两篇文章。当我输入标题时,这些帖子已显示为可能的答案。但对我来说,这个话题还不清楚,所以我特地问了。很乐意得到一些额外的具体答案。谢谢 @Armin:方式 这个问题中有太多单独的、不相关的问题。如果有不清楚的地方,作为关于该特定事物的问题。您真正想要的是对范围 for 的工作原理的逐步重新解释,仅限于这个特定用例。 【参考方案1】:

您的解决方案的主要问题是您尝试以非标准的方式进行处理,这种方式有很多限制,并且可能难以被那些对迭代器在 STL 中如何工作有深入了解的专家使用。

我将尝试以一种技术含量较低的方式回答大多数问题,这应该有助于理解它在实践中的工作原理。

1) 共享类型会导致一些问题,因为某些算法可能需要多个活动迭代器。此外,它不尊重 SRP(单一责任原则),因此不是一个好的设计实践。

2) 它只需要返回本质上类似于迭代器的东西。

3) 或者如果数据在内存中是连续的,它可以是一个指针。

4) 通常begin 函数按值返回一个迭代器。

5) 通常end 函数会返回一个迭代器到末尾。如果 end 不是真正的位置(例如输入流或容器的最后一个值是哨兵),它也可以返回哨兵对象。

6) 您可以将自己的 typedef/aliases 放入类中并根据需要使用它们。

7) 运算符 * 的返回值类型几乎总是与迭代器类型不同。

然后是一些意见/建议

在您的情况下,LinkedList 将是迭代器类型(或者您可以使用包装器)。 如果您希望能够知道大小而不必迭代整个列表,则通常您希望容器不仅仅是一个迭代器。此外,容器可能会提供一些优化的成员函数,例如 std::list 中的 sort。 如果您想了解专家的做法,STL 源代码可能是一个很好的来源。 您的代码不尊重constness,因此如果您将Example example; 替换为const Example example;,将无法正常工作。 您的大多数运算符在声明中不遵循通常的约定。 您的begin 函数有副作用。 使用您的代码,您将无法将迭代器存储到列表中的某个位置。这意味着排序或删除匹配项等算法可能会失败。 将void 放在空参数列表中本质上是一种过时的方式来编写不应再在C++ 中使用的代码。它只在 C 中有用。 Operator++ 通常应该返回对此的引用。 如果您想使用哨兵作为最终值,最好使用您自己的类型(enum 值可以做到)。否则,它会允许意外比较像 example != 25 那样编译(在这种情况下,当 k 的值为 25 时,循环可能会结束),因此这会使代码更难理解。 为什么不使用std::forward_list 而不是重新发明***。 如果您确实需要使用 LinkedList,那么 STL 可能是有关如何正确定义迭代器的宝贵信息来源。

【讨论】:

非常感谢您的评价和提示。只是为了澄清:提供的代码只是一个例子。纯粹用于测试目的。我想了解基于 for 的范围如何在后台工作。再次感谢

以上是关于基于 C++17 中的范围,用于自定义容器或具有不同开始/结束类型的通用类的主要内容,如果未能解决你的问题,请参考以下文章

C ++中的排名树

用于匹配单词的 javascript 正则表达式模式,具有自定义单词边界

用于解释自定义 c++11 属性的 Clang/GCC 插件

SwiftUI-自定义容器

基于不适用于 System.Web.UI.Control 对象的 CA2000“在失去范围之前处理对象”创建自定义 FXCop 规则

iOS 范围检测具有预定义接近度 UDID 的信标