Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 448
user3072517 Avatar asked Jan 28 '26 06:01

user3072517


1 Answers

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.


Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!