Современная повторяющаяся коллекция c ++ с фильтром - PullRequest
3 голосов
/ 14 июля 2020

Допустим, у меня есть этот дизайн класса

class A {};
class B : public A {};
class C : public A {};

и такой контейнер A

std::list<A *> elements;

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

Класс c будет

for (auto it = elements.begin(); it != elements.end(); ++it) {
  B * b = dynamic_cast<B *>(*it);
  if (b) {
    // do stuff
  }
}

Одна идея, которая приходит мне в голову, - это создать класс итератора полученный из стандарта, который фильтрует, но это было бы сложно. Никаких ограничений на уровне языка C ++ (C ++ 20 тоже может подойти, но было бы здорово увидеть ответы C ++ 11). Обычный c ++ и stl, пожалуйста (я знаю, что boost имеет конструкцию foreach if, но).

Ответы [ 5 ]

2 голосов
/ 14 июля 2020

Возможная реализация c ++ 20 с использованием диапазона

#include <iostream>
#include <list>
#include <ranges>

struct A {
  virtual ~A() = default;
};
struct B : public A {
  void foo() const { std::cout << "B\n"; }
};
struct C : public A {};

int main() {
  std::list<A *> demo{new A{}, new B{}, new C{}, new B{}};
  auto is_B = [](const A *p) { return dynamic_cast<const B *>(p) != nullptr; };
  auto get_B_const = [](const A *p) { return dynamic_cast<const B *>(p); };

  for (auto p_B :
       demo | std::views::filter(is_B) | std::views::transform(get_B_const)) {
    p_B->foo();
  }
  // demo destruction with delete not shown
}

Выводит:

B

B

Демо : https://godbolt.org/z/6oP8hj

Примечание: если производительность имеет значение, вы можете избежать использования dynamic_cast два раза с помощью

 auto get_B_const = [](const A *p) { 
    assert(dynamic_cast<const B *>(p));
    return static_cast<const B *>(p);
 };
1 голос
/ 14 июля 2020

Я могу добавить 2 цента: обычно это пахнет недостатком дизайна (конечно, есть исключения), эта проблема "разнородного контейнера" ​​пока не имеет "хорошего" решения. Что-то, что я видел в th wilds, - это то, что поверх std:vector<A*> va со всеми элементами вы можете поддерживать другой вектор только с объектами «B *», std::vector<B*> vb, когда пришло время повторить go для vb когда пришло время удалить go для va

1 голос
/ 14 июля 2020

Одно из возможных решений без dynamic_cast. Но следует позаботиться о том, чтобы указать правильный тип в конструкторах производных классов.

И я бы рекомендовал использовать std :: unique_ptr, если список действительно хранит объекты класса.

class Base
{
public:
  enum class Type
  {
    A,
    B,
    C
  };
  Base() = delete;
  virtual ~Base() = default;    
  Type type() const { return _type; }

protected:
  Base(Type type) : _type{type} {}  

private:
  Type _type;
};

class A : public Base
{
public:
  A() : Base{Base::Type::A} {}
};

class B : public Base
{
public:
  B() : Base{Base::Type::B} {}
};
class C : public Base
{
public:
  C() : Base{Base::Type::C} {}
};
            
    void function() 
    {
       std::list<std::unique_ptr<Base>> list;
       list.emplace_back(std::make_unique<A>());
       list.emplace_back(std::make_unique<B>());
       list.emplace_back(std::make_unique<C>());
                   
       // use non-const iterators if you intend to modify the object
       std::for_each(std::cbegin(list), std::cend(list),
                     [](const auto &item)
                     {
                       switch (item->type()) 
                       {
                         case Base::Type::B: 
                         {
                           assert(dynamic_cast<B*>(item.get()));
                           const auto &b = static_cast<B*>(item.get());
                           // do staff with b
                           break;
                         }
          
                         default:
                           return;
                         }                          
                   });
    }
1 голос
/ 14 июля 2020

Я думаю, что в C ++ 11 описанный вами способ максимально приближен, но я могу ошибаться в этом. C ++ 17 значительно расширил библиотеку алгоритмов , так что вы можете использовать std::for_each.

Чтобы продемонстрировать это, давайте дадим классам немного функциональности и создадим вектор (или список ) экземпляров:

class A {
public:
    virtual std::string name() const = 0;
};
class B : public A {
public:
    virtual std::string name() const override {
        return "Class B";
    }
};
class C : public A {
public:
    virtual std::string name() const override {
        return "Class C";
    }
};

int main()
{
    std::vector<A*> vec { new B(), new B(), new C(), new C(), new B() };
}

Теперь, используя for_each, вы можете переписать свой l oop:

std::for_each(std::begin(vec), std::end(vec), [](const A* val) {
    auto B* b = dynamic_cast<B*>(val);

    if (b)
        std::cout << b->name() << std::endl;
});

К сожалению, нет встроенного фильтра для любого из алгоритмы. Однако вы можете реализовать что-то вроде for_each_if:

template<typename Iterator, typename Predicate, typename Operation> void 
for_each_if(Iterator begin, Iterator end, Predicate pred, Operation op) {
    std::for_each(begin, end, [&](const auto p) {
        if (pred(p))
            op(p);
    });
}

И использовать это так:

for_each_if(std::begin(vec), std::end(vec), 
    [](A* val) { return dynamic_cast<B*>(val) != nullptr; },
    [](const A* val) {
        std::cout << val->name() << std::endl;
    }
);

Или для вашего конкретного случая c вы можете специализировать реализацию даже больше:

template<typename T, typename Iterator, typename Operation> void 
dynamic_for_each(Iterator begin, Iterator end, Operation op) {
    std::for_each(begin, end, [&](auto p) {
        auto tp = dynamic_cast<T>(p);
        
        if (tp)
            op(tp);
    });
}

и используйте его так:

dynamic_for_each<B*>(std::begin(vec), std::end(vec), [](const B* val) {
    std::cout << val->name() << std::endl;
});

Все три реализации выводят один и тот же результат:

Class B
Class B
Class B
0 голосов
/ 14 июля 2020

Вам не нужно отливать, если вы правильно поняли дизайн:

struct A { 
    virtual void doSomethingWithB() = 0;
    virtual ~A() = default;
};
struct B : A {
    void doSomethingWithB() override {
        // do somehting
    }
};
struct C : A {
    void doSomethingWithB() override {
       // do nothing !
    }
};

Тогда ваш l oop будет просто:

for (auto elem : elements) {
    elem->doSomethingWithB();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...