【问题标题】:Vector of pointers to base type, find all instances of a given derived type stored in a base type指向基类型的指针向量,查找存储在基类型中的给定派生类型的所有实例
【发布时间】:2014-04-27 15:50:39
【问题描述】:

假设您在库中有一个基类:

class A {};

和派生类

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

现在BC 的实例存储在boost::shared_ptr<A> 的std::vector 中:

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

BC 实例的添加由用户完成,无法提前确定添加它们的顺序。

但是,在库内部,可能需要对BC 执行特定操作,因此需要将指向基类的指针强制转换为BC

我当然可以进行“试错”转换,即尝试转换为BC(以及基类的任何其他派生类),直到找到不会抛出的转换。但是,这种方法看起来很粗略且容易出错,我正在寻找一种更优雅(性能更好)的方法。

我正在寻找同样适用于 C++98 的解决方案,但可能涉及增强功能。

有什么想法吗?


编辑:

好的,感谢到目前为止的所有答案!

我想提供一些关于用例的更多细节。所有这些都发生在参数优化的背景下。

用户通过以下方式定义优化问题:

  • 指定参数,即它们的类型(例如“受约束的双精度”、“受约束的整数”、“不受约束的双精度”、“布尔”等)和初始值
  • 指定评估函数,将一个或多个评估(双值)分配给给定的参数集

不同的优化算法然后作用于问题定义,包括它们的参数。

有许多用于常见情况的预定义参数对象,但用户也可以通过从我的一个基类派生来创建自己的参数对象。所以从库的角度来看,除了参数对象需要遵守给定的(基类)API这一事实之外,我不能对参数对象做出太多假设。

问题定义是用户定义的 C++ 类,派生自具有 std::vector 接口的基类。用户添加他的(预定义的或自创的)参数对象并重载适应度函数。

可能会访问参数对象

  • 从优化算法内部(通常还可以,即使对于自创的参数对象,因为派生的参数对象需要为其值提供访问函数)。
  • 从用户提供的适应度函数中(通常可以,因为用户知道在哪里可以找到集合中的哪个参数对象及其值可以轻松访问)

这很好用。

然而,在某些特殊情况下,

  • 用户想要访问他自己开发的参数类型的详细信息
  • 第三方提供了参数结构(这是一个开源库,其他人可能会针对特定的优化问题添加代码)
  • 参数结构(即哪些参数在向量中的位置)可以作为优化问题的一部分进行修改 --> 示例:神经网络架构的训练

在这种情况下,最好有一个简单的方法来访问基类型集合中给定派生类型的所有参数对象。

我已经有一个模板化的“conversion_iterator”。它遍历基础对象的向量并跳过那些不符合所需目标类型的对象。但是,这是基于“试错”转换(即我检查转换后的智能指针是否为 NULL),我觉得这非常不雅且容易出错。

我希望有更好的解决方案。

注意:优化库的目标是用例,其中给定参数集的评估步骤可能持续任意长(通常是几秒钟,可能是几小时或更长时间)。因此,访问参数类型的速度并不是什么大问题。但是稳定性和可维护性是...

【问题讨论】:

  • 除了virtual 方法,你的意思是?

标签: c++ boost base-class


【解决方案1】:

没有比尝试投射并查看它是否成功更好的通用解决方案。您也可以派生出动态typeid 并依次将其与所有类型进行比较,但这实际上是相同的工作量。

更根本的是,您需要这样做暗示了一个设计问题:基类的全部目的是能够像对待父母一样对待孩子。但在某些情况下这是必要的,在这种情况下,您可以使用 visitor 来调度它们。

【讨论】:

  • 仅在启用 RTTI 时有效。 IMO,您的第二点是 100% 正确的。
  • 如果没有启用 RTTI,那它就不是真正的 C++。
  • “更根本的是,你需要这样做暗示了一个设计问题”——完全是这样!
  • 是的,typeid 应该可以解决问题(以一种比强制转换更优雅的方式),因为它使用了转换迭代器。只有一个比较,所以我想这很好。谢谢!关于“设计问题”:只有在特殊情况下的特殊用户才需要此设施。当然,所有其他访问都通过基类发生(请参阅我在 OP 中的编辑)。所以我希望这是合理的。
【解决方案2】:

如果可能,将虚拟方法添加到类A 以执行“对BC 的特定操作”。

如果不可能或不合理,请使用dynamic_cast的指针形式,因此不涉及异常。

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

【讨论】:

    【解决方案3】:

    添加 B 和 C 的实例由用户完成,无法提前确定添加的顺序。

    好的,那就把它们放在两个不同的容器里?

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

    然后您可以遍历 B 或 C 以满足您的需求。

    【讨论】:

      【解决方案4】:

      我建议在基类中实现一个方法(例如 TypeOf()),它将返回特定对象的类型。确保您将该方法定义为虚拟和抽象,以便强制您在派生类型中实现。至于类型本身,您可以为每种类型(例如类)定义一个枚举。

      enum class ClassType { ClassA, ClassB, ClassC };
      

      【讨论】:

        【解决方案5】:

        您可能会对这个答案感兴趣:Generating an interface without virtual functions?

        这向您展示了两种方法

        • 单个集合中带有访问者的变体
        • 单独的集合,

        正如其他人(特别是 Fred 和 Konrad)所建议的那样。后者对迭代更有效,前者可能更纯粹和可维护。它甚至可能更有效,具体取决于使用模式。

        【讨论】:

          猜你喜欢
          • 2014-06-16
          • 2021-07-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-05-20
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多