web-dev-qa-db-fra.com

Est-ce une bonne pratique de stocker des copies des mêmes pointeurs partagés dans différents vecteurs?

J'ai une classe de base, BaseObject, et deux classes dérivées DerivedObject1 et DerivedObject2. Ils partagent un comportement et des méthodes communs, mais DerivedObject1 a une méthode supplémentaire. Ma classe principale MyClass stocke (dans std::vector) boost::shared_ptr des instances de ces classes. MyClass doit appeler commonMethod() pour tout le BaseObject, et parfois aussi additionalMethod() pour tout DerivedObject1.

class BaseObject
{
  virtual void commonMethod();
}

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

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

Existe-t-il des inconvénients à avoir deux vecteurs dans MyClass, un qui stocke TOUS les pointeurs de DerivedObject1 et DerivedObject2 et un autre vecteur ne stockant que les pointeurs de DerivedObject1? Ce qui signifie que j'aurais tous les pointeurs DerivedObject1 deux fois. Mais je pense que l'appel aux différentes méthodes serait clair, au moins.

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();
    }
  }
}

Je peux penser à d’autres moyens de le faire, principalement en ayant un vecteur pour DerivedObject1 et un vecteur pour DerivedObject2, mais pour appeler commonMethod(), il me faudrait parcourir les deux vecteurs. Ma solution initiale me semble la meilleure, sauf que certains pointeurs sont stockés deux fois. Quels sont les inconvénients de ceci?

7
Enaid

Question intéressante. Nous sommes parfois confrontés à une telle situation en ce qui concerne le maintien des codes hérités, dont nous ne savons pas qui a écrit.

Y at-il des inconvénients à avoir deux vecteurs dans MyClass…?

Je pense qu'il n'y a pas d'inconvénients mécaniques (ou de performances). Si nous avons du mal à respecter le délai de publication, nous n'avons pas d'autre choix que de choisir un moyen aussi simple. Cependant, stocker deux fois les mêmes vecteurs réduit réellement la maintenabilité et nous devrions envisager de l'améliorer à l'avenir.


  • std :: dynamic_pointer_cast (ou boost :: dynamic_pointer_cast) [Démo]

Si vous avez besoin de dynamic_pointer_cast pour implémenter des fonctions telles que @Caleth 's Push_back/remove, Que diriez-vous de supprimer only_derived1 et d'appliquer à contrecœur un seul dynamic_pointer_cast dans doSomethingForDerivedObject1() de la manière suivante? .] Cela simplifiera MyClass. La modification requise ne sera pas compliquée si DerivedObject3 est défini à l'avenir.

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();
        }
    }
}

Déclaration de la fonction virtuelle BaseObject::additionalMethod() et implémentation

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

vous pouvez alors à nouveau supprimer only_derived1. Dans cette méthode, vous devez implémenter DerivedObject3::additionalMethod() uniquement si DerivedObject3 est défini.

Mais, bien que cela dépende de votre code constructeur ou setter, si le cas suivant se produit également

everything;    ->derived2
only_derived1; ->derived1

cette méthode est encore insuffisante.


  • Modification de la conception [Demo] _

Idéalement, nous ne devrions pas utiliser l'héritage public pour implémenter des objets dans les relations "IS-ALMOST-A", comme le dit Herb Sutter. La relation entre BaseObject, DerivedObject1 et DerivedObject2 ressemble à celle-ci. Depuis Je ne connais peut-être pas le code complet de votre application, mais il serait utile d'envisager d'extraire DerivedObject1::additionalMethod() en tant que classe ou de pointeur de fonction et de placer son vecteur dans MyClass en tant que membre privé.

6
Hiroki

Je peux suggérer ceci: stockez tout dans un tableau et créez une additionalMethod() factice dans DerivedObject2. Ensuite, appelez simplement additionalMethod pour chaque objet.

Alternativement: 

Ils partagent un comportement et des méthodes communs, mais DerivedObject1 a une méthode supplémentaire

Faire DerivedObject1 hériter de DerivedObject2

1
sagi

Oui, votre première méthode est bonne, le problème principal est que vous finissez par dupliquer les membres publics de vector pour vous assurer de la cohérence du vecteur 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... 
};

Ce que vous pouvez faire à la place est de fournir une vue de votre bases en utilisant quelque chose comme: les adaptateurs 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
Caleth