Хорошая ли практика хранить копии одних и тех же общих указателей в разных векторах? - PullRequest
0 голосов
/ 03 октября 2018

У меня есть базовый класс BaseObject и два производных класса DerivedObject1 и DerivedObject2.Они имеют общее поведение и методы, но у DerivedObject1 есть дополнительный метод.Мой основной класс MyClass хранит (в std::vector) boost::shared_ptr экземпляров этих классов.MyClass необходимо вызвать commonMethod() для всех BaseObject, а иногда вызвать additionalMethod() для всех DerivedObject1.

class BaseObject
{
  virtual void commonMethod();
}

Class DerivedObject1 : public BaseObject
{
  void commonMethod();
  void additionalMethod();
}

Class DerivedObject2 : public BaseObject
{
  void commonMethod();
}

Есть ли недостатки в наличии двух векторов в MyClass, один из которых хранит ВСЕ указатели DerivedObject1 и DerivedObject2, а другой - только указатели DerivedObject1?Это значит, что у меня были бы все DerivedObject1 указатели дважды.Но я думаю, что вызов различных методов был бы понятен, по крайней мере.

class MyClass
{
  typedef std::vector<std::shared_ptr<BaseObject>> BaseObjectVector;
  typedef std::vector<std::shared_ptr<DerivedObject1>> DerivedObject1Vector;
  BaseObjectVector everything;
  DerivedObject1Vector only_derived1;

  void doSomething()
  {
    for (BaseObjectVector::iterator iter = everything.begin(); iter != everything.end(); ++iter)
    {
      (*iter)->commonMethod();
    }
  }

  void doSomethingForDerivedObject1()
  {
    for (DerivedObject1Vector::iterator iter = only_derived1.begin(); iter != only_derived1.end(); ++iter)
    {
      (*iter)->additionalMethod();
    }
  }
}

Я могу придумать другие способы сделать это, в основном один вектор для DerivedObject1 и один вектор для DerivedObject2, но для вызова commonMethod() мне пришлось бы перебирать оба вектора.Мое оригинальное решение кажется мне лучшим, за исключением того, что некоторые указатели хранятся дважды.Каковы недостатки этого?

Ответы [ 3 ]

0 голосов
/ 03 октября 2018

Да, ваш первый метод хорош, основная проблема заключается в том, что вы в конечном итоге дублируете публичные члены vector, чтобы обеспечить согласованность вектора Derived.

class MyClass
{
    typedef std::vector<std::shared_ptr<BaseObject>> BaseObjectVector;
    typedef std::vector<std::shared_ptr<DerivedObject1>> DerivedObject1Vector;
    BaseObjectVector everything;
    DerivedObject1Vector only_derived1;
public:
    void push_back(shared_ptr<Base> ptr)
    {
        everything.push_back(ptr);
        if (shared_ptr<Derived1> derived = dynamic_ptr_cast<Derived1>(ptr))
        {
            only_derived1.push_back(derived);
        }
    }
    void remove(shared_ptr<Base> ptr)
    {
        base.remove(ptr);
        only_derived1.remove(dynamic_ptr_cast<Derived1>(ptr));
    }
    // dozens more... 
};

Что вывместо этого можно сделать вид вашего bases, используя что-то вроде boost::range адаптеров

shared_ptr<Derived1> convert(shared_ptr<Base> ptr)
{
    return dynamic_ptr_cast<Derived1>(ptr);
}

bool not_null(shared_ptr<Derived1> ptr)
{
    return ptr.get();
}

boost::for_each(bases 
              | boost::adaptors::transformed(convert) // Base to Derived
              | boost::adaptors::filtered(not_null)   // Eliminate null
              | boost::adaptors::indirected,          // dereference
                boost::bind(&Derived1::additionalMethod, boost::placeholders::_1));
0 голосов
/ 03 октября 2018

Интересный вопрос.Иногда мы сталкиваемся с такой ситуацией в обслуживании устаревших кодов, о которых мы не знаем, кто написал.

Есть ли недостатки наличия двух векторов в MyClass…?

Я думаю, что нет никаких механических (или эксплуатационных) недостатков. Если мы изо всех сил пытаемся уложиться в сроки выпуска, у нас нет иного выбора, кроме как выбрать такой простой способ.Тем не менее, сохранение одинаковых векторов в два раза фактически снижает удобство сопровождения, и мы должны рассмотреть возможность его улучшения в будущем.


  • std :: dynamic_pointer_cast (или boost :: dynamic_pointer_cast) [Демо]

Если вам нужно dynamic_pointer_cast для реализации таких функций, как @ Caleth push_back / remove, как насчет удаления only_derived1и неохотно применяя только один dynamic_pointer_cast в doSomethingForDerivedObject1() с самого начала следующим образом?Это сделает MyClass проще.Требуемая модификация не будет сложной, если DerivedObject3 будет определен в будущем.

void MyClass::doSomethingForDerivedObject1()
{
    for (const auto& obj_i : everything)
    {
      if (auto derived1 = std::dynamic_pointer_cast<DerivedObject1>(obj_i))
        {
           derived1->additionalMethod();
        }
    }
}

void MyClass::doSomethingForDerivedObject3()
{
    for (const auto& obj_i : everything)
    {
      if (auto derived3 = std::dynamic_pointer_cast<DerivedObject3>(obj_i))
        {
           derived3->additionalMethod();
        }
    }
}

Объявление виртуальной функции BaseObject::additionalMethod() и реализация

void DerivedObject2::additionalMethod()
{ 
  /* nothing to do */
}

, после чего вы можете снова удалить only_derived1.В этом методе вы должны реализовать DerivedObject3::additionalMethod(), только если определен DerivedObject3.

Но, хотя это зависит от кода вашего конструктора или установщика, если следующий случай также произойдет

everything;    ->derived2
only_derived1; ->derived1

thisметод все еще недостаточен.


В идеале, мы не должны использовать публичное наследование для реализации объектов внутри "IS-ALMOST-A "отношения, как говорит Херб Саттер. Отношение между BaseObject, DerivedObject1 и DerivedObject2 выглядит следующим образом. Поскольку я не знаю весь код вашего приложения, я могу ошибаться, но этоСтоит рассмотреть возможность извлечения DerivedObject1::additionalMethod() в качестве другого класса или указателя функции и помещения его вектора в MyClass в качестве закрытого члена.

0 голосов
/ 03 октября 2018

Я могу предложить следующее: хранить все в одном массиве и создать пустышку additionalMethod() в DerivedObject2.Затем - просто вызовите additionalMethod для каждого объекта.

Альтернативно:

Они имеют общее поведение и методы, но DerivedObject1 имеет дополнительный метод

Сделать DerivedObject1 наследовать от DerivedObject2

...