【问题标题】:Is it good practice to store copies of the same shared pointers in different vectors?将相同共享指针的副本存储在不同的向量中是一种好习惯吗?
【发布时间】:2025-11-25 12:20:03
【问题描述】:

我有一个基类BaseObject,以及两个派生类DerivedObject1DerivedObject2。它们共享一个共同的行为和方法,但DerivedObject1 有一个额外的方法。我的主类MyClass 存储(在std::vectorboost::shared_ptr 这些类的实例。 MyClass 需要为所有 BaseObject 调用 commonMethod(),有时为所有 DerivedObject1 调用 additionalMethod()

class BaseObject
{
  virtual void commonMethod();
}

Class DerivedObject1 : public BaseObject
{
  void commonMethod();
  void additionalMethod();
}

Class DerivedObject2 : public BaseObject
{
  void commonMethod();
}

MyClass 中有两个向量,一个存储DerivedObject1DerivedObject2 的所有指针,另一个只存储DerivedObject1 的指针有什么缺点吗?这意味着我将拥有所有 DerivedObject1 指针两次。但我认为至少对不同方法的调用是明确的。

class MyClass
{
  typedef std::vector<std::shared_ptr<BaseObject>> BaseObjectVector;
  typedef std::vector<std::shared_ptr<DerivedObject1>> DerivedObject1Vector;
  BaseObjectVector everything;
  DerivedObject1Vector only_derived1;

  void doSomething()
  {
    for (BaseObjectVector::iterator iter = everything.begin(); iter != everything.end(); ++iter)
    {
      (*iter)->commonMethod();
    }
  }

  void doSomethingForDerivedObject1()
  {
    for (DerivedObject1Vector::iterator iter = only_derived1.begin(); iter != only_derived1.end(); ++iter)
    {
      (*iter)->additionalMethod();
    }
  }
}

我可以想到其他方法来做到这一点,主要是为DerivedObject1 设置一个向量,为DerivedObject2 设置一个向量,但是要调用commonMethod(),我必须遍历这两个向量。我最初的解决方案对我来说似乎是最好的,除了一些指针被存储了两次。这样做有什么缺点?

【问题讨论】:

  • 为什么 DerivedObject1 不能从 DerivedObject2 继承?那么问题不就消失了吗?
  • 有什么理由不能将additionalMethod() 提升到基类中并使其成为DerivedObject2 的无用函数?
  • 请向我们展示而不是描述您的代码。最好采用Minimal, Complete, and Verifiable Example 的形式。也请阅读how to ask good questions,以及this question checklist
  • 是的,抱歉,这是 boost::shared_ptr,将编辑,我在版本之间切换并感到困惑
  • Visitor pattern 可能是向基类添加新虚拟方法的替代方法。

标签: c++ shared-ptr c++03


【解决方案1】:

有趣的问题。 我们在维护遗留代码时有时会遇到这种情况,我们不知道是谁编写的。

MyClass 中有两个向量有什么缺点吗?

我认为没有机械(或性能)缺点。 如果我们很难赶上发布期限,我们别无选择,只能选择这样一种简单的方式。 但是,将相同的向量存储两次实际上会降低可维护性,我们应该考虑在未来改进它。


  • std::dynamic_pointer_cast(或 boost::dynamic_pointer_cast)[Demo]

如果需要dynamic_pointer_cast实现@Calethpush_back/remove等功能, 如何从一开始就删除only_derived1 并不情愿地在doSomethingForDerivedObject1() 中仅应用一个dynamic_pointer_cast,如下所示? 这将使 MyClass 更简单。如果将来定义 DerivedObject3,所需的修改不会很复杂。

void MyClass::doSomethingForDerivedObject1()
{
    for (const auto& obj_i : everything)
    {
      if (auto derived1 = std::dynamic_pointer_cast<DerivedObject1>(obj_i))
        {
           derived1->additionalMethod();
        }
    }
}

void MyClass::doSomethingForDerivedObject3()
{
    for (const auto& obj_i : everything)
    {
      if (auto derived3 = std::dynamic_pointer_cast<DerivedObject3>(obj_i))
        {
           derived3->additionalMethod();
        }
    }
}

声明虚函数BaseObject::additionalMethod()并实现

void DerivedObject2::additionalMethod()
{ 
  /* nothing to do */
}

然后您可以再次删除only_derived1。 在此方法中,只有定义了 DerivedObject3 时,您才必须实现 DerivedObject3::additionalMethod()

但是,虽然这取决于你的构造函数或者setter代码,但是如果下面的情况也会出现

everything;    ->derived2
only_derived1; ->derived1

这个方法还是不够。


理想情况下,正如 Herb Sutter 所说,我们不应该使用公共继承来实现“IS-ALMOST-A”关系中的对象。 BaseObjectDerivedObject1DerivedObject2 之间的关系看起来像这样。 由于我不知道您应用程序的整个代码,我可能是错的,但值得考虑将 DerivedObject1::additionalMethod() 提取为另一个类或函数指针,并将其向量作为私有成员放入 MyClass。

【讨论】:

    【解决方案2】:

    我可以建议:将所有内容存储在一个数组中,并在 DerivedObject2 中创建一个虚拟 additionalMethod()。然后 - 只需为每个对象调用 additionalMethod

    另外:

    它们共享一个共同的行为和方法,但 DerivedObject1 有一个额外的方法

    使DerivedObject1继承自DerivedObject2

    【讨论】:

    • 我想过制作一个虚拟方法,但它感觉不像是漂亮的代码,而且它会聚集类,additionalMethod() 对于DerivedObject2 真的没有任何意义。我担心将其添加到 DerivedObject2 会使阅读文档的人感到困惑。同样让DerivedObject1DerivedObject2 继承也没有多大意义(比如如果基类是 Fruit,而派生类是 Apple 和 Banana)。另外,将来可能会有DerivedObject3。尽管如此,这些都是解决方案,但我想知道拥有多个共享 ptr 副本是否存在实际缺点
    • @Enaid 缺点是浪费空间和同步向量的复杂性。您必须将相同的指针添加到两个向量,然后如果您删除一个元素,您需要从两个向量中执行此操作。额外的空间和复杂性是它的缺点。
    【解决方案3】:

    是的,您的第一种方法很好,主要问题是您最终复制了vector 的公共成员以确保Derived 向量的一致性。

    class MyClass
    {
        typedef std::vector<std::shared_ptr<BaseObject>> BaseObjectVector;
        typedef std::vector<std::shared_ptr<DerivedObject1>> DerivedObject1Vector;
        BaseObjectVector everything;
        DerivedObject1Vector only_derived1;
    public:
        void push_back(shared_ptr<Base> ptr)
        {
            everything.push_back(ptr);
            if (shared_ptr<Derived1> derived = dynamic_ptr_cast<Derived1>(ptr))
            {
                only_derived1.push_back(derived);
            }
        }
        void remove(shared_ptr<Base> ptr)
        {
            base.remove(ptr);
            only_derived1.remove(dynamic_ptr_cast<Derived1>(ptr));
        }
        // dozens more... 
    };
    

    您可以改为使用boost::range's adaptors 之类的方式提供bases 的视图

    shared_ptr<Derived1> convert(shared_ptr<Base> ptr)
    {
        return dynamic_ptr_cast<Derived1>(ptr);
    }
    
    bool not_null(shared_ptr<Derived1> ptr)
    {
        return ptr.get();
    }
    
    boost::for_each(bases 
                  | boost::adaptors::transformed(convert) // Base to Derived
                  | boost::adaptors::filtered(not_null)   // Eliminate null
                  | boost::adaptors::indirected,          // dereference
                    boost::bind(&Derived1::additionalMethod, boost::placeholders::_1));
    

    【讨论】:

    最近更新 更多