Как сделать для каждой функции цикла в C++ работу с пользовательским классом

Я новичок в программировании на C/C++, но я программирую на С# уже 1,5 года. Мне нравится С#, и мне нравится класс List, поэтому я подумал о создании класса List в C++ в качестве упражнения.

List<int> ls;
int whatever = 123;
ls.Add(1);
ls.Add(235445);
ls.Add(whatever);

Реализация похожа на любой класс Array List. У меня есть элемент T* vector котором я храню элементы, и когда это хранилище собирается быть полностью заполненным, я изменяю его размер.

Пожалуйста, обратите внимание, что это не должно использоваться в производстве, это всего лишь упражнение. Я хорошо знаю vector<T> и друзей.

Теперь я хочу просмотреть элементы моего списка. Я не люблю использовать for(int i=0;i<n; i==). Я напечатал for в Visual Studio, ожидаемый для Intellisense, и он предложил мне это:

for each (object var in collection_to_loop)
{

}        

Это очевидно не будет работать с моей реализацией List. Я подумал, что мог бы сделать немного магии, но это похоже на огромный взлом. На самом деле, больше всего меня беспокоит передача такого типа:

#define foreach(type, var, list)\
int _i_ = 0;\
##type var;\
for (_i_ = 0, var=list[_i_]; _i_<list.Length();_i_++,var=list[_i_]) 

foreach(int,i,ls){
    doWork(i);
}

Мой вопрос: есть ли способ заставить этот пользовательский класс List работать с циклом, foreach-like?

Ответ 1

Во-первых, синтаксис цикла for-each в C++ отличается от C# (он также называется range based for loop. Он имеет форму:

for(<type> <name> : <collection>) { ... }

Так, например, с помощью std::vector<int> vec, это будет что-то вроде:

for(int i : vec) { ... }

Под обложками это эффективно использует функции begin() и end(), которые возвращают итераторы. Следовательно, чтобы ваш пользовательский класс использовал цикл for-each, вам необходимо предоставить функцию begin() и end(). Они обычно перегружены, возвращая либо iterator либо const_iterator. Реализация итераторов может быть сложной, хотя с векторно-подобным классом это не слишком сложно.

template <typename T>
struct List
{
    T* store;
    std::size_t size;
    typedef T* iterator;
    typedef const T* const_iterator;

    ....

    iterator begin() { return &store[0]; }
    const_iterator begin() const { return &store[0]; }
    iterator end() { return &store[size]; }
    const_iterator end() const { return &store[size]; }

    ...
 };

С помощью этих функций вы можете использовать цикл, основанный на диапазоне, как указано выше.

Ответ 2

Пусть iterable будет типа Iterable. Затем, чтобы сделать

for (Type x : iterable)

компиляции, должны быть типы, называемые Type и IType и должны быть функции

IType Iterable::begin()
IType Iterable::end()

IType должен обеспечивать функции

Type operator*()
void operator++()
bool operator!=(IType)

Вся конструкция - действительно сложный синтаксический сахар для чего-то вроде

for (IType it = iterable.begin(); it != iterable.end(); ++it) {
    Type x = *it;
    ...
}

где вместо Type любой совместимый тип (такой как const Type или Type&), который будет иметь ожидаемые последствия (константа, ссылка вместо копии и т.д.).

Так как все расширение происходит синтаксически, вы также можете немного изменить объявление операторов, например, * вернуть ссылку или иметь! = Взять const IType& rhs мере необходимости.

Обратите внимание: вы не можете использовать форму for (Type& x: iterable) если *it не возвращает ссылку (но если она возвращает ссылку, вы также можете использовать копию).

Обратите также внимание на то, что operator++() определяет префиксную версию оператора ++ однако он также будет использоваться в качестве постфиксного оператора, если вы явно не определяете постфикс ++. Ранжирование не будет компилироваться, если вы поставляете только постфикс ++, который btw.can может быть объявлен как operator++(int) (аргумент dummy int).


Минимальный рабочий пример:

#include <stdio.h>
typedef int Type;

struct IType {
    Type* p;
    IType(Type* p) : p(p) {}
    bool operator!=(IType rhs) {return p != rhs.p;}
    Type& operator*() {return *p;}
    void operator++() {++p;}
};

const int SIZE = 10;
struct Iterable {
    Type data[SIZE];

    IType begin() {return IType(data); }
    IType end() {return IType(data + SIZE);}
};

Iterable iterable;

int main() {
    int i = 0;
    for (Type& x : iterable) {
        x = i++;
    }
    for (Type x : iterable) {
        printf("%d", x);
    }
}

вывод

0123456789

Вы можете подделать ряды для каждого (например, для более старых C++ компиляторов) со следующим макросом:

 #define ln(l, x) x##l // creates unique labels
 #define l(x,y)  ln(x,y)
 #define for_each(T,x,iterable) for (bool _run = true;_run;_run = false) for (auto it = iterable.begin(); it != iterable.end(); ++it)\
     if (1) {\
         _run = true; goto l(__LINE__,body); l(__LINE__,cont): _run = true; continue; l(__LINE__,finish): break;\
         } else\
            while (1)   \
                if (1) {\
                    if (!_run) goto l(__LINE__,cont);/* we reach here if the block terminated normally/via continue */   \
                    goto l(__LINE__,finish);/* we reach here if the block terminated by break */\
                }   \
                else\
                l(__LINE__,body): for (T x = *it;_run;_run=false) /* block following the expanded macro */                         

 int main() {
     int i = 0;
     for_each(Type&, x, iterable) {
         i++;
         if (i > 5) break;
         x = i;
     }
     for_each(Type, x, iterable) {
         printf("%d", x);
     }
     while (1);
 }

(используйте declspec или передайте IType, если ваш компилятор даже не имеет авто).

Вывод:

 1234500000

Как вы можете видеть, continue и break будут работать с этим благодаря сложной конструкции. См. Http://www.chiark.greenend.org.uk/~sgtatham/mp/ для дальнейшего взлома C-препроцессора для создания настраиваемых структур управления.

Ответ 3

Этот синтаксис, предложенный Intellisense, не является C++; или это расширение MSVC.

C++ 11 имеет диапазон для циклов для итерации по элементам контейнера. Вам нужно реализовать функции-члены begin() и end() для вашего класса, которые возвратят итераторы в первый элемент, а один - за последний элемент соответственно. Это, конечно же, означает, что вам нужно также внедрять подходящие итераторы для вашего класса. Если вы действительно хотите пойти по этому маршруту, вы можете посмотреть на Boost.IteratorFacade; это уменьшает боль от реализации итераторов самостоятельно.

После этого вы сможете это написать:

for( auto const& l : ls ) {
  // do something with l
}

Кроме того, поскольку вы новичок в C++, я хочу убедиться, что вы знаете, что стандартная библиотека имеет несколько классов контейнеров.

Ответ 4

C++ не имеет for_each loop for_each в своем синтаксисе. Вы должны использовать C++ 11 или использовать функцию шаблона std :: for_each.

#include <vector>
#include <algorithm>
#include <iostream>

struct Sum {
    Sum() { sum = 0; }
    void operator()(int n) { sum += n; }

    int sum;
};

int main()
{
    std::vector<int> nums{3, 4, 2, 9, 15, 267};

    std::cout << "before: ";
    for (auto n : nums) {
        std::cout << n << " ";
    }
    std::cout << '\n';

    std::for_each(nums.begin(), nums.end(), [](int &n){ n++; });
    Sum s = std::for_each(nums.begin(), nums.end(), Sum());

    std::cout << "after:  ";
    for (auto n : nums) {
        std::cout << n << " ";
    }
    std::cout << '\n';
    std::cout << "sum: " << s.sum << '\n';
}

Ответ 5

Как предлагает @yngum, вы можете заставить VC++ for each расширения работать с любым произвольным типом коллекции, определяя методы begin() и end() в коллекции, чтобы вернуть пользовательский итератор. Ваш итератор, в свою очередь, должен реализовать необходимый интерфейс (оператор разыменования, оператор инкремента и т.д.). Я сделал это, чтобы обернуть все классы коллекции MFC для устаревшего кода. Это немного работы, но это можно сделать.