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

Siu*_*ji- 19 c++ polymorphism inheritance boost reference-counting

要求

  1. 我正在写一个名为RCObject"引用计数对象"的类;
  2. 该类RCObject应该是抽象的,作为框架的基类(EC++ 3 Item 7);
  3. RCObject应禁止在堆栈上创建子类的实例(MEC++ 1 Item 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
    };
    
    Run Code Online (Sandbox Code Playgroud)
  4. CLARIFIED:RCObject应该禁止声明/返回指向(和子类)的原始指针(不幸的是,我认为没有一种实用的方法来强制执行它,即当用户不遵循时触发编译错误).参见上面第3项中的示例源代码;

  5. RCObject子类的实例应该像CloneableJava 一样可以克隆.(MEC++ 1第25项);
  6. 用户子类化RCObject应该能够为其子类编写"工厂方法".即使忽略返回的值(未分配给变量),也不应有内存泄漏.与此相近的机制是autoreleaseObjective-C;

    [ 补充: cschwanKos指出,返回"智能指针到RCObject"足以满足要求.]

  7. CLARIFIED:RCObject子类的实例应该能够包含在适当的std::boost::容器中.我主要需要一个"类似" std::vector的容器,一个"类似" std::set的容器和一个"类似" std::map的容器.基线就是这样

    intrusive_ptr<RCObject> my_bear = v[10];
    
    Run Code Online (Sandbox Code Playgroud)

    m["John"] = my_bear;
    
    Run Code Online (Sandbox Code Playgroud)

    按预期工作;

  8. 源代码应该可以使用C++ 98编译器进行编译,该编译器支持有限的C++ 11(确切地说,是Visual Studio 2008和gcc 4.6).

更多信息

类定义

namespace zoo {
    class RCObject { ... };                  // Abstract
    class Animal : public RCObject { ... };  // Abstract
    class Bear : public Animal { ... };      // Concrete
    class Panda : public Bear { ... };       // Concrete
}
Run Code Online (Sandbox Code Playgroud)

"非智能"版本 - 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;
}
Run Code Online (Sandbox Code Playgroud)

"非智能"版本 - 主要()

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;
}
Run Code Online (Sandbox Code Playgroud)

"智能"版本[不完整!]

/* 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;
}
Run Code Online (Sandbox Code Playgroud)

上面的代码("智能版")显示了预期的使用模式.我不确定这种使用模式是否遵循使用智能指针的最佳实践.如果没有,请纠正我.

类似的问题

参考

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

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

用品

  • [ atomic ]:boost::atomicBoost.org的 用法示例

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

Yak*_*ont 2

make_shared在与引用计数器相同的分配块中创建类的实例。我不确定为什么您认为intrusive_ptr会有更好的性能:当已经存在无法删除的引用计数机制时,这很好,但这里的情况并非如此。

对于克隆,我将其实现为一个自由函数,该函数接受智能指针并返回相同的值。它是一个朋友,并调用基类中的私有纯虚拟克隆方法,该方法返回基类的共享指针,然后执行快速智能指针转换为派生的共享指针。如果您更喜欢克隆作为方法,请使用 crtp 复制此方法(为私有克隆命名,例如secret_clone)。这为您提供了协变智能指针返回类型,并且开销很小。

具有一系列基类的 Crtp 通常会让您同时传入基类和派生类。crtp 类从基类派生,并具有self()返回派生类的通常功能。

工厂函数应该返回智能指针。您可以使用自定义删除器技巧来获取预销毁方法调用以进行最后的清理。

如果您完全偏执,您可以阻止大多数获取原始指针或对类的引用的方法:阻止智能指针上的运算符*。那么到达原始类的唯一途径是显式调用operator->.

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

请注意,悬空弱指针会阻止共享内存被回收。

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