user3072517
user3072517

Reputation: 533

Passing vectors of derived shared pointers?

What is the proper way to allow vectors of shared pointers to a derived class to get passed to a function which is expecting a vector of shared pointers to a base class without performing a copy?

Here is the code:

#include <string>
#include <vector>
#include <memory>

class Base {
public:
    std::string Name;
};
using BaseList = std::vector<std::shared_ptr<Base>>;

class Derived : Base {
};
using DerivedList = std::vector<std::shared_ptr<Derived>>;

class BaseHandler {
public:
    void process( BaseList list ) {
    }
};

int main() {

    DerivedList list;

    BaseHandler bh;
    bh.process( list );

}

Code Link: http://coliru.stacked-crooked.com/a/5a5b18ba3b2a4f08

EDIT: DOH!!! I posted the wrong one. Sorry about that...here is the shared_ptr one.

Upvotes: 4

Views: 2169

Answers (1)

user2486888
user2486888

Reputation:

You may try this.

template <class T,
        class SharedPtr = typename T::value_type,
        class Element = typename SharedPtr::element_type,
        class IsDerived = typename std::enable_if<std::is_base_of<Base, Element>::value, void*>::type
    >
void process(const T& t) { std::cout << "process" << std::endl; }

The key ideas are:

  1. Instead of accessing the elements through base class pointers, we can access the them through their concrete type information known by the compiler.
  2. This function template uses a trick called "SFINAE" to check whether the parameter is a container of smart pointer of derived class.


Follow up:

Conversion from "container of shared pointer to derived class" to "container of shared pointer to base class" is possible and not very difficult. However, I concern whether the design choice of using "container of shared pointer to base class" will give you acceptable performance.


Let's discuss how to do it first.

  1. We can create a std::shared_ptr<Base> object from each std::shared_ptr<Derived> object by using std::static_pointer_cast.
  2. To apply std::static_pointer_cast on everything entries of the list, we can use std::transform.
  3. If you have many derived classes, the conversion can be made available to every derived classes by using a function template with SFINAE check as mentioned.

So, the code looks like:

DerivedList derivedList;
// Put items into derivedList

BaseList baseList;
baseList.reserve(derivedList.size());
std::transform(std::begin(derivedList), std::end(derivedList), std::back_inserter(baseList),
    [](const std::shared_ptr<Derived>& shptr)
    {
        return std::static_pointer_cast<Base>(shptr);
    });

BaseHandler bh;
bh.process(baseList);

Or:

class BaseForwarder
{
public:
    template <class T,
            class SharedPtr = typename T::value_type,
            class Element = typename SharedPtr::element_type,
            class IsDerived = typename std::enable_if<std::is_base_of<Base, Element>::value, void*>::type
        >
    void process(const T& derivedList)
    {
        BaseList baseList;
        baseList.reserve(derivedList.size());

        std::transform(std::begin(derivedList), std::end(derivedList), std::back_inserter(baseList),
                [](const SharedPtr& shptr)
                {
                    return std::static_pointer_cast<Base>(shptr);
                });

        BaseHandler bh;
        bh.process(baseList);
    }
};

However, this approach has quite a lot of performance penalty.

  • A new list of pointer to base class has to be created for each list of pointer to derived class. It spends a lot of time and memory to construct the new list.
  • The objects are accessed through pointers. This indirection slow things down.
  • Since the objects are not allocated into compact data structure, cache misses will be severe.

I suggest you to evaluate the performance to see whether this approach is acceptable. Otherwise, it is better to consider some other design choices.

Upvotes: 2

Related Questions