【问题标题】:What would be the safest way to store objects of classes derived from a common interface in a common container?将从公共接口派生的类的对象存储在公共容器中的最安全方法是什么?
【发布时间】:2011-01-30 04:17:06
【问题描述】:

我想在一个公共容器中管理一堆从共享接口类派生的类对象。

为了说明问题,假设我正在构建一个包含不同演员的游戏。让我们调用接口IActor 并从中派生EnemyCivilian

现在,我的想法是让我的游戏主循环能够做到这一点:

// somewhere during init
std::vector<IActor> ActorList;
Enemy EvilGuy; 
Civilian CoolGuy;
ActorList.push_back(EvilGuy);
ActorList.push_back(CoolGuy);

// main loop
while(!done) {
    BOOST_FOREACH(IActor CurrentActor, ActorList) {
        CurrentActor.Update();
        CurrentActor.Draw();
    }
}

...或类似的东西。这个例子显然行不通,但这正是我在这里问的原因。

我想知道:在通用异构容器中管理这些对象的最佳、最安全、最高级别的方法是什么?我知道各种方法(Boost::Any、void*、带有 boost::shared_ptr 的处理程序类、Boost.Pointer Container、dynamic_cast),但我无法决定哪种方法可以去这里。

另外我想强调的是,我想尽可能远离手动内存管理或嵌套指针。

帮助非常感谢:)。

【问题讨论】:

    标签: c++ interface containers heterogeneous


    【解决方案1】:

    我的即时反应是您应该将智能指针存储在容器中,并确保基类定义了足够的(纯)虚方法,您永远不需要将dynamic_cast 回到派生类。

    【讨论】:

      【解决方案2】:

      为了解决你提到的问题,虽然你的方向是对的,但是你做错了。这就是你需要做的事情

      • 使用虚函数定义一个基类(您已经在做),在您的情况下,这些虚函数将被派生类EnemyCivilian 覆盖。
      • 您需要选择一个合适的容器来存放您的对象。您选择了std::vector&lt;IActor&gt;,这不是一个好的选择,因为
        • 首先,当您将对象添加到向量时,会导致对象切片。这意味着只存储EnemyCivilianIActor 部分,而不是整个对象。
        • 其次,您需要根据对象的类型 (virtual functions) 调用函数,这只有在使用指针时才会发生。

      上述两个原因都表明您需要使用可以包含指针的容器,例如 std::vector&lt;IActor*&gt; 。但更好的选择是使用container of smart pointers,这将使您免于内存管理问题。您可以根据需要使用任何智能指针(但不能使用auto_ptr

      这就是你的代码的样子

      // somewhere during init
      std::vector<some_smart_ptr<IActor> > ActorList;
      ActorList.push_back(some_smart_ptr(new Enemy()));
      ActorList.push_back(some_smart_ptr(new Civilian()));
      

      // main loop
      while(!done) 
      {
          BOOST_FOREACH(some_smart_ptr<IActor> CurrentActor, ActorList) 
          {
              CurrentActor->Update();
              CurrentActor->Draw();
           }
      }
      

      这与您的原始代码非常相似,除了智能指针部分

      【讨论】:

      • 我喜欢你的方法,因为它对于基于指针的方法来说看起来非常干净。然而,正如其他人所指出的,我想要做的可能是使用 Boost Pointer Containers 做得更好,因为它们确实似乎是为我想要实现的目标而设计的。如果失败,我会尝试你的方法。或者你有什么理由使用 Boost Pointer Containers?
      • 即使我认为 Boost Pointer Containers 方法更优越,并且在语法上更易于使用
      • 然而在某些 STL 算法中可能无法使用 Boost 指针容器boost.org/doc/libs/1_42_0/libs/ptr_container/doc/…
      【解决方案3】:

      如果您希望容器独占其中的元素,请使用 Boost 指针容器:它们是为这项工作而设计的。否则,使用shared_ptr&lt;IActor&gt; 的容器(当然要正确使用它们,这意味着需要共享所有权的每个人都使用shared_ptr)。

      在这两种情况下,请确保 IActor 的析构函数是虚拟的。

      void* 要求您进行手动内存管理,所以没有了。当类型通过继承相关时,Boost.Any 是多余的 - 标准多态可以完成这项工作。

      您是否需要dynamic_cast 是一个正交问题 - 如果容器的用户只需要 IActor 接口,并且您要么 (a) 将接口的所有功能设为虚拟,要么 (b) 使用非虚拟接口成语,那么你不需要dynamic_cast。如果容器的用户知道某些 IActor 对象是“真正的”平民,并且想要使用 Civilian 界面中的东西而不是 IActor,那么您将需要演员表(或重新设计)。

      【讨论】:

        【解决方案4】:

        正如您所猜测的,您需要将对象存储为指针。
        我更喜欢使用 boost 指针容器(而不是普通的智能指针容器)。

        原因是 boost ptr 容器像访问对象(返回引用)而不是指针一样访问对象。这使得在容器上使用标准函子和算法变得更加容易。

        智能指针的缺点是您共享所有权。
        这不是你真正想要的。您希望所有权位于一个位置(在本例中为容器)。

        boost::ptr_vector<IActor> ActorList; 
        ActorList.push_back(new Enemy()); 
        ActorList.push_back(new Civilian());
        

        std::for_each(ActorList.begin(), 
                      ActorList.end(),
                      std::mem_fun_ref(&IActor::updateDraw));
        

        【讨论】:

        • 喜欢你使用 for_each 的方式
        • 您能解释一下您的 for_each 以及如何使用 BOOST_FOREACH 来使用它吗?
        • std::for_each(I1,I2,Action)。将 Action 应用于 I1 和 I2 之间(不包括 I2)范围内的所有值(在本例中调用方法 updateDraw)。其中 I1, I2 是迭代器。见:sgi.com/tech/stl/for_each.html
        • 你应该问另一个关于如何使用 BOOST_FOREACH() 的问题。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-01
        • 2011-08-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-09-20
        相关资源
        最近更新 更多