用于智能指针的抽象基类(intrusive_ptr) - 处理继承,多态,可克隆性和从工厂方法返回

时间:2013-03-18 10:33:37

标签: c++ inheritance boost polymorphism reference-counting

要求

  1. 我正在写一个名为RCObject的课程,代表"参考计数对象&#34 ;;
  2. RCObject应该是抽象的,作为框架的基类(EC++3第7项);
  3. 应禁止在堆栈上创建RCObject子类的实例(MEC++1第27项);

    [已添加:]

    [假设BearRCObject]

    的具体子类

    [C.E.这里表示编译错误]

    Bear b1;                        // Triggers C.E. (by using MEC++1 Item 27)
    Bear* b2;                       // Not allowed but no way to trigger C.E.
    intrusive_ptr<Bear> b3;         // Recommended
    
    Bear* bs1 = new Bear[8];                   // Triggers C.E.
    container< intrusive_ptr<RCObject> > bs2;  // Recommended
    intrusive_ptr_container<RCObject> bs3;     // Recommended
    
    class SomeClass {
    private:
        Bear m_b1;                 // Triggers C.E.
        Bear* m_b2;                // Not allowed but no way to trigger C.E.
        intrusive_ptr<Bear> m_b3;  // Recommended
    };
    
  4. CLARIFIED:应该禁止声明/返回指向RCObject(和子类)的原始指针(很遗憾,我不认为存在实用的强制执行方式)它,即当用户不遵循时触发编译错误)。请参阅上面第3项中的示例源代码;

  5. RCObject子类的实例应该像Java中的Cloneable一样可以克隆。 (MEC++1项目25);
  6. 子类化RCObject的用户应该能够为其子类编写"Factory Methods"。即使忽略返回的值(未分配给变量),也不应有内存泄漏。与Objective-C相近的机制是autorelease;

    [已添加 cschwanKos指出将&#34;智能指针返回到RCObject&#34;足以满足要求。 ]

  7. CLARIFIED: RCObject子类的实例应该能够包含在适当的std::boost::容器中。我主要需要一个&#34; std::vector - 喜欢&#34;容器,&#34; std::set - 喜欢&#34;容器和&#34; std::map - 喜欢&#34;容器。基线就是

    intrusive_ptr<RCObject> my_bear = v[10];
    

    m["John"] = my_bear;
    

    按预期工作;

  8. 源代码应该使用支持C ++ 11的C ++ 98编译器进行编译(确切地说是Visual Studio 2008和gcc 4.6)。
  9. 更多信息

    • 我是Boost的初学者(Boost很大,我需要一些时间来熟悉它。可能存在开箱即用的解决方案,但它很有可能我我不知道这样的解决方案);
    • 出于性能方面的考虑,我想使用intrusive_ptr代替shared_ptr,但我对他们都持开放态度,甚至是其他任何建议;
    • 我不知道make_shared()allocate_shared()enable_shared_from_this()是否有帮助(顺便提一下,enable_shared_from_this()似乎没有在Boost中得到高度推广 - 它不能甚至可以在smart pointer主页中找到;
    • 我听说过#34;编写自定义分配器&#34;但我担心它太复杂了;
    • 我想知道RCObject是否应该私下从boost::noncopyable继承;
    • 我无法找到满足我所有要求的任何现有实施;
    • 框架是游戏引擎;
    • 主要目标平台是Android和iOS;
    • 我知道intrusive_ptr_add_ref()intrusive_ptr_release()以及如何使用Argument-Dependent Lookup (aka. Koenig Lookup)实现它们;
    • 我知道如何将boost::atomic_size_tboost:intrusive_ptr一起使用。

    类定义

    namespace zoo {
        class RCObject { ... };                  // Abstract
        class Animal : public RCObject { ... };  // Abstract
        class Bear : public Animal { ... };      // Concrete
        class Panda : public Bear { ... };       // Concrete
    }
    

    &#34;非智能&#34;版本 - createAnimal()[工厂方法]

    zoo::Animal* createAnimal(bool isFacingExtinction, bool isBlackAndWhite) {
        // I wish I could call result->autorelease() at the end...
        zoo::Animal* result;
    
        if (isFacingExtinction) {
            if (isBlackAndWhite) {
                result = new Panda;
            } else {
                result = new Bear;
            }
        } else {
            result = 0;
        }
    
        return result;
    }
    

    &#34;非智能&#34;版本 - main()

    int main() {
        // Part 1 - Construction
        zoo::RCObject* object1 = new zoo::Bear;
        zoo::RCObject* object2 = new zoo::Panda;
        zoo::Animal* animal1 = new zoo::Bear;
        zoo::Animal* animal2 = new zoo::Panda;
        zoo::Bear* bear1 = new zoo::Bear;
        zoo::Bear* bear2 = new zoo::Panda;
        //zoo::Panda* panda1 = new zoo::Bear;  // Should fail
        zoo::Panda* panda2 = new zoo::Panda;
    
        // Creating instances of RCObject on the stack should fail by following
        // the method described in the book MEC++1 Item 27.
        //
        //zoo::Bear b;                         // Should fail
        //zoo::Panda p;                        // Should fail
    
        // Part 2 - Object Assignment
        *object1 = *animal1;
        *object1 = *bear1;
        *object1 = *bear2;
        //*bear1 = *animal1;                   // Should fail
    
        // Part 3 - Cloning
        object1 = object2->clone();
        object1 = animal1->clone();
        object1 = animal2->clone();
        //bear1 = animal1->clone();            // Should fail
    
        return 0;
    }
    

    &#34;智能&#34;版本[Incomplete!]

    /* TODO: How to write the Factory Method? What should be returned? */
    
    #include <boost/intrusive_ptr.hpp>
    
    int main() {
        // Part 1 - Construction
        boost::intrusive_ptr<zoo::RCObject> object1(new zoo::Bear);
        boost::intrusive_ptr<zoo::RCObject> object2(new zoo::Panda);
        /* ... Skip (similar statements) ... */
        //boost::intrusive_ptr<zoo::Panda> panda1(new zoo::Bear); // Should fail
        boost::intrusive_ptr<zoo::Panda> panda2(new zoo::Panda);
    
        // Creating instances of RCObject on the stack should fail by following
        // the method described in the book MEC++1 Item 27. Unfortunately, there
        // doesn't exist a way to ban the user from declaring a raw pointer to
        // RCObject (and subclasses), all it relies is self discipline...
        //
        //zoo::Bear b;                         // Should fail
        //zoo::Panda p;                        // Should fail
        //zoo::Bear* pb;                       // No way to ban this
        //zoo::Panda* pp;                      // No way to ban this
    
        // Part 2 - Object Assignment
        /* ... Skip (exactly the same as "non-smart") ... */
    
        // Part 3 - Cloning
        /* TODO: How to write this? */
    
        return 0;
    }
    

    上述代码(&#34; Smart Version&#34;)显示了预期的使用模式。我不确定这种使用模式是否遵循使用智能指针的最佳实践。如果没有,请纠正我。

    类似问题

    参考

    • [boost::enable_shared_from_this()]:有效的C ++:55改进程序和设计的具体方法(第3版)作者:Scott Meyers
      • 第7项:在多态基类中声明析构函数

    • [EC++3]:更有效的C ++:改进程序和设计的35种新方法(第1版)作者:Scott Meyers
      • 第25项:虚拟化构造函数和非成员函数
      • 第27项:要求或禁止基于堆的对象。

    文章

    • [MEC++1]:boost::atomic的使用示例 - Boost.org

    • [section]:用于提升代码的智能指针 - CodeProject
      • [CP8394]:intrusive_ptr - 轻量级共享指针

    • [section]:C ++中入侵参考计数对象的基类 - Dobb博士

2 个答案:

答案 0 :(得分:2)

make_shared在与引用计数器相同的分配块中创建类的实例。我不确定为什么你认为intrusive_ptr会有更好的表现:当你已经有一些你无法删除的引用计数机器时它很棒,但这不是这里的情况。

对于克隆,我会将它实现为一个免费的函数,它需要一个智能的pojnter并返回相同的内容。它是一个朋友,并在base中调用私有的纯虚拟克隆方法,返回一个指向base的共享指针,然后执行快速智能指针转换为派生的共享指针。如果您更喜欢克隆作为方法,请使用crtp复制此方法(为私有克隆提供类似secret_clone的名称)。这为您提供了协变智能指针返回类型,而且开销很小。

具有一系列基类的Crtp通常会传递基类和派生类.crtp类派生自base并且具有返回派生的通常self()

工厂函数应该返回智能指针。你可以使用自定义删除技巧来获得一个pre-destroy methid调用以进行上次清理。

如果你是完全偏执狂,你可以阻止大多数方法来获得一个原始指针或类的引用:智能指针上的块运算符*。然后,到原始类的唯一途径是显式调用operator->

另一种需要考虑的方法是unique_ptr并引用它们。您需要共享所有权和终身管理吗?它确实使一些问题变得更简单(共享所有权)。

请注意,悬空的弱指针可防止内存被共享带来回收。

始终使用智能指针的一个严重缺点是您不能直接在容器内部使用堆栈实例或实例。这两者都可以带来严重的性能提升。

答案 1 :(得分:1)

  
      
  1. 应禁止在堆栈上创建RCObject子类的实例([MEC ++ 1] [mec ++ 1] Item 27);
  2.   

你的理由是什么? MEC ++给出了“能够自杀的对象”的例子,这可能在游戏框架的背景下有意义。是这样的吗?

如果你坚持避免使用更简单的解决方法,应该可以使用足够智能的智能指针。

请注意,如果是这种情况,您可能还希望禁止使用new[]在堆栈上创建此类对象的数组 - 这也可以防止删除单个对象。您可能还希望禁止将RCObject用作子对象(其他类中的成员)。这意味着您完全不允许使用RCObject值,并且客户端代码只能通过智能指针处理它们。

  
      
  1. 应该避免声明/返回指向RCObject(和子类)的原始指针(不幸的是,我认为没有办法通过发出编译错误来强制执行它);​​
  2.   

然后你必然会有一些弱点指示说“我对这个对象很感兴趣,但我不能让它保持活力”。

  
      
  1. 子类化RCObject的用户应该能够为其子类编写[“Factory Methods”] [factory_method]。即使忽略返回的值(未分配给变量),也不应有内存泄漏。
  2.   

此类函数将返回一个临时智能指针对象,引用计数等于1.如果此临时值不用于初始化另一个(从而进一步递增引用计数),它将清理对象。你很安全。

  
      
  1. RCObject子类的实例应该能够包含在std::boost::容器中(或任何适当的容器)。我主要需要类似于std::vectorstd::setstd::map;
  2. 的内容   

这种不同意(3)。如果你坚持要在堆上单独创建对象并通过智能指针(而不是值)传递,那么你还应该使用智能指针的容器。

  
      
  • 出于性能方面的考虑,我想使用[intrusive_ptr] [intrusive_ptr]而不是[shared_ptr] [shared_ptr],但我对他们两个甚至任何其他建议都持开放态度。
  •   

你不是过早优化吗?

此外,我相信使用侵入式指针会消除使用弱引用的可能性 - 正如我之前提到的那样,这很可能是你需要的。

  
      
  • 我想知道RCObject是否应该私下从[boost::noncopyable] [noncopyable]继承;
  •   

如果您不允许使用值类型的变量并提供虚拟克隆,那么可能不需要公共副本构造函数。您可以制作私有副本ctor并在定义克隆时使用它。