20

要求

  1. 我正在编写一个名为 的类RCObject,它代表“引用计数对象”;
  2. 该类RCObject应该是抽象的,作为框架的基类(EC++3 Item 7);
  3. RCObject应禁止在堆栈上创建子类的实例( MEC++1第 27 条);

    [添加: ]

    [假设Bear是一个具体的子类RCObject]

    [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. 澄清:应禁止声明/返回指向RCObject(和子类)的原始指针(不幸的是,我认为不存在一种实用的方法来执行它,即当用户不遵循时触发编译错误)。请参阅上面第 3 项中的示例源代码;

  5. 子类的实例RCObject应该是可克隆的,就像Cloneable在 Java 中一样。(MEC++1第 25 条);
  6. 子类化的用户RCObject应该能够为他们的子类编写“工厂方法”。即使返回值被忽略(未分配给变量),也不应该有内存泄漏。与此接近的机制是autorelease在 Objective-C 中。

    [添加: cschwanKos指出返回“智能指针RCObject”足以满足要求。]

  7. 澄清:子类的实例RCObject应该能够包含在适当的std::容器boost::中。我主要需要一个“ std::vector-like”容器,一个“ std::set-like”容器和一个“ std::map-like”容器。基线是

    intrusive_ptr<RCObject> my_bear = v[10];
    

    m["John"] = my_bear;
    

    按预期工作;

  8. 源代码应该可以使用具有有限 C++11 支持的 C++98 编译器进行编译(确切地说是 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
}

“非智能”版本 - 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;
}

“非智能”版本 - 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;
}

“智能”版【未完成!】

/* 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;
}

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

类似问题

参考

  • [ 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 - 轻量级共享指针

  • [ DrDobbs ]:C++ 中侵入式引用计数对象的基类 - Dobb 博士的
    • [第1]:第1页
    • [第2]:第2页
    • [第3]:第3页
4

2 回答 2

2

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

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

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

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

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

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

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

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

于 2013-03-18T11:13:12.170 回答
1
  1. RCObject应禁止在堆栈上创建子类的实例([MEC++1][mec++1] Item 27);

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

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

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

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

那么你肯定会有弱指针来表达“我对这个对象感兴趣,但我不会让它活着”。

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

这样的函数将返回一个引用计数等于 1 的临时智能指针对象。如果这个临时对象不用于初始化另一个对象(从而进一步增加引用计数),它将清理对象。你安全了。

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

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

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

你不是过早优化吗?

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

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

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

于 2013-03-18T14:16:24.570 回答