【问题标题】:Passing vectors of derived shared pointers?传递派生共享指针的向量?
【发布时间】:2015-05-03 14:47:42
【问题描述】:

什么是允许指向派生类的共享指针向量传递给一个函数的正确方法,该函数期望一个指向基类的共享指针向量而不执行复制?

代码如下:

#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 );

}

代码链接:http://coliru.stacked-crooked.com/a/5a5b18ba3b2a4f08

编辑:DOH!!!我发错了。很抱歉...这里是 shared_ptr 之一。

【问题讨论】:

  • 没有办法,无论是否合适。
  • 复制什么? std::shared_ptr的?复制它们没什么大不了的(顺便说一句,它们的共享状态没有被深度复制)。
  • 注意:vector不是vector的基类。
  • 啊……对了!那么,我是否必须遍历它们并将它们复制到新向量才能做到这一点?
  • 如果是这样,那就太糟糕了。我也想知道这个。但我认为你不能做得更好。

标签: c++ pointers c++11 vector


【解决方案1】:

你可以试试这个。

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

关键思想是:

  1. 我们可以通过编译器知道的具体类型信息来访问它们,而不是通过基类指针访问元素。
  2. 此函数模板使用一种称为“SFINAE”的技巧来检查参数是否为派生类智能指针的容器。


跟进

从“到派生类的共享指针的容器”到“到基类的共享指针的容器”的转换是可能的并且不是很困难。但是,我担心使用“指向基类的共享指针的容器”的设计选择是否会给您带来可接受的性能。


让我们先讨论如何做。

  1. 我们可以使用std::static_pointer_cast从每个std::shared_ptr&lt;Derived&gt;对象创建一个std::shared_ptr&lt;Base&gt;对象。
  2. 要将std::static_pointer_cast 应用于列表的所有条目,我们可以使用std::transform
  3. 如果您有许多派生类,则可以通过使用带有上述 SFINAE 检查的函数模板,使转换可用于每个派生类。

所以,代码如下:

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

或者:

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

但是,这种方法有相当多的性能损失。

  • 必须为每个派生类的指针列表创建一个新的基类指针列表。构建新列表会花费大量时间和内存。
  • 通过指针访问对象。这种间接方式会减慢速度。
  • 由于对象没有分配到紧凑的数据结构中,缓存未命中将很严重。

我建议您评估性能,看看这种方法是否可以接受。否则,最好考虑其他一些设计选择。

【讨论】:

  • 我觉得这很有趣....但是,它实际上并没有将其转换为基类的 shared_ptr,所以现在这会强制将“进程”的实现放在头文件中(我想避免的事情)。我在这里 coliru.stacked-crooked.com/a/571ec96419f8224d 有点玩弄它,它仍然以 std::vector&lt;shared_ptr&lt;Derived&gt;&gt; 传递它。在我的情况下,我正在处理这个向量中可能有数以亿计的成员,我真的想避免复制它们。我会使用实际的基类,但我只需要它们的一些额外属性。
  • @user3072517 我已将答案扩展为响应。
  • 如果我想保留继承权,似乎没有太多方法可以制作副本。这似乎是一种耻辱,如果没有这样的性能冲击就无法完成。为了更好地理解为什么要问它是因为这些向量包含查询结果集中每条记录中的所有字段。这些的派生版本包含一个“已修改”标志。它们是作为派生集创建的,但我现在需要使用来自数据库的响应来加载它们。该加载发生在基类上,我不想在派生类上重复加载逻辑。
  • 在类型和指针安全的情况下,也会带来灵活性损失。
  • @user3072517 您可能会提出另一个问题,通过提及该问题背后的具体问题来寻找替代设计选择。但就目前而言,这是我能做的最好的了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-06
  • 1970-01-01
  • 2018-08-25
  • 1970-01-01
相关资源
最近更新 更多