简体   繁体   中英

Covariant wrapper for std::vector

I want a covariant wrapper for std::vector . My idea was to do something like this:

  • Create an abstract base class BaseVector<B> whose begin and end methods just forward to pure virtual functions.
  • Create a concrete derived class DerivedVector<B, D> that wraps a std::vector<D> . Its begin and end methods would shadow the base class's, and it would implement the virtual methods that the base class forwards to.

In this way, if you have a pointer to a BaseVector you can iterate over base class instances, and if you have a DerivedVector pointer you can iterate over derived class instances.

( EDIT : Obviously the BaseVector will necessarily not support insertion, since it can't know the type of the objects it contains. Perhaps this means "Vector" isn't the best name for it; I'm open to suggestions. Thanks @CygnusX1.)

For the DerivedVector class the begin and end methods could just forward to std::vector 's.

Question: How could I implement the methods the BaseVector begin and end forward to? Do I have to write my own iterator class that wraps the std::vector iterators?

Alternatively: Is there a better or easier way to do this?

A DerivedVector<B, D> instance needs to be usable through a BaseVector<B> pointer in code that knows about B but not D , and it needs to have equivalent performance to just holding a derived class vector (if the calling code knew about the derived class)

Sample use case :

A library provides a BaseWidget class and a BaseWidgetPool class, which the user derives from. Due to the nature of the library, in any given program there will be exactly one DerivedWidget class, but this class will differ for every program using the library.

The BaseWidgetPool class includes methods that iterate over all the widgets in the pool and make use of their BaseWidget functionality. But each program's DerivedWidgetPool is likely to want to use the DerivedWidget functionality of its contents.

(I know I could also handle this by making a generic WidgetPool<T> class, but I'd rather encapsulate T to the part of the code which actually uses it.)

With ranges, this become a non issue. It has all the api you need for polymorphic ranges:

#include <range/v3/all.hpp>

namespace rv = ranges::view;

int main() {
    std::vector<Derived> derived_container;

    // ... fill it

    ranges::any_view<Base*> base_view =
        rv::all(derived_container) | rv::transform([](auto& e) -> Base* { return &e; });

    // do stuff with `base_view`
}

Then you can use base_view as long as derived_container lives.

I used any_view so that the range is type erased. You can pass it around without needing to templatize function and you can pass it in virtual functions.

If you cannot use std ranges nor range v3, I'd create a simialar wrapper. So instead of a exposing a class herarchy, you hide it in the utility class.

Live example

Of course, the any_view is there only to avoid templating function over the range type when passing it around. You can instead directly materialize the range into a vector:

auto base_view =
    rv::all(derived_container) | rv::transform([](auto& e) -> Base* { return &e; });

std::vector<Base*> base_container = ranges::to<std::vector>(base_view);

This will avoid overhead during iteration but require a memory allocation for the new vector.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM