Vector of pointers to base type, find all instances of a given derived type stored in a base type

StackOverflow https://stackoverflow.com/questions/23325469

  •  10-07-2023
  •  | 
  •  

Pergunta

Suppose you have a base class inside of a library:

class A {};

and derived classes

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

Now Instances of B and C are stored in a std::vector of boost::shared_ptr<A>:

std::vector<boost::shared_ptr<A> > A_vec;
A_vec.push_back(boost::shared_ptr<B>(new B()));
A_vec.push_back(boost::shared_ptr<C>(new C()));

Adding instances of B and C is done by a user, and there is no way to determine in advance the order, in which they will be added.

However, inside of the library, there may be a need to perform specific actions on B and C, so the pointer to the base class needs to be casted to B and C.

I can of course do "trial and error" conversions, i.e. try to cast to Band C(and any other derivative of the base class), until I find a conversion that doesn't throw. However, this method seems very crude and error-prone, and I'm looking for a more elegant (and better performing) way.

I am looking for a solution that will also work with C++98, but may involve boost functionality.

Any ideas ?


EDIT:

O.k., thanks for all the answers so far!

I'd like to give some more details regarding the use-case. All of this happens in the context of parametric optimization.

Users define the optimization problem by:

  • Specifying the parameters, i.e. their types (e.g. "constrained double", "constrained integer", "unconstrained double", "boolean", etc.) and initial values
  • Specifying the evaluation function, which assigns one or more evaluations (double values) to a given parameter set

Different optimization algorithms then act on the problem definitions, including their parameters.

There is a number of predefined parameter objects for common cases, but users may also create their own parameter objects, by deriving from one of my base classes. So from a library perspective, apart from the fact that the parameter objects need to comply with a given (base-class) API, I cannot assume much about parameter objects.

The problem definition is a user-defined C++-class, derived from a base-class with a std::vector interface. The user adds his (predefined or home-grown) parameter objects and overloads a fitness-function.

Access to the parameter objects may happen

  • from within the optimization algorithms (usually o.k., even for home-grown parameter objects, as derived parameter objects need to provide access functions for their values).
  • from within the user-supplied fitness function (usually o.k., as the user knows where to find which parameter object in the collection and its value can be accessed easily)

This works fine.

There may however be special cases where

  • a user wants to access specifics of his home-grown parameter types
  • a third party has supplied the parameter structure (this is an Open Source library, others may add code for specific optimization problems)
  • the parameter structure (i.e. which parameters are where in the vector) may be modified as part of the optimization problem --> example: training of the architecture of a neural network

Under these circumstances it would be great to have an easy method to access all parameter objects of a given derived type inside of the collection of base types.

I already have a templated "conversion_iterator". It iterates over the vector of base objects and skips those that do not comply with the desired target type. However, this is based on "trial and error" conversion (i.e. I check whether the converted smart pointer is NULL), which I find very unelegant and error-prone.

I'd love to have a better solution.

NB: The optimization library is targetted at use-cases, where the evaluation step for a given parameter set may last arbitrarily long (usually seconds, possibly hours or longer). So speed of access to parameter types is not much of an issue. But stability and maintainability is ...

Foi útil?

Solução

There’s no better general solution than trying to cast and seeing whether it succeeds. You can alternatively derive the dynamic typeid and compare it to all types in turn, but that is effectively the same amount of work.

More fundamentally, your need to do this hints at a design problem: the whole purpose of a base class is to be able to treat children as if they were parents. There are certain situations where this is necessary though, in which case you’d use a visitor to dispatch them.

Outras dicas

If possible, add virtual methods to class A to do the "specific actions on B and C".

If that's not possible or not reasonable, use the pointer form of dynamic_cast, so there are no exceptions involved.

for (boost::shared_ptr<A> a : A_vec)
{
    if (B* b = dynamic_cast<B*>(a.get()))
    {
        b->do_something();
    }
    else if (C* c = dynamic_cast<C*>(a.get()))
    {
        something_else(*c);
    }
}

Adding instances of B and C is done by a user, and there is no way to determine in advance the order, in which they will be added.

Okay, so just put them in two different containers?

std::vector<boost::shared_ptr<A> > A_vec;
std::vector<boost::shared_ptr<B> > B_vec;
std::vector<boost::shared_ptr<C> > C_vec;

void add(B * p)
{
    B_vec.push_back(boost::shared_ptr<B>(p));
    A_vec.push_back(b.back());
}

void add(C * p)
{
    C_vec.push_back(boost::shared_ptr<C>(p));
    A_vec.push_back(c.back());
}

Then you can iterate over the Bs or Cs to your hearts content.

I would suggest to implement a method in the base class (e.g. TypeOf()), which will return the type of the particular object. Make sure you define that method as virtual and abstract so that you will be enforced to implement in the derived types. As for the type itself, you can define an enum for each type (e.g. class).

enum class ClassType { ClassA, ClassB, ClassC };

This answer might interest you: Generating an interface without virtual functions?

This shows you both approaches

  • variant w/visitor in a single collection
  • separate collections,

as have been suggested by others (Fred and Konrad, notably). The latter is more efficient for iteration, the former could well be more pure and maintainable. It could even be more efficient too, depending on the usage patterns.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top