哪种类型的指针用于实现对集合元素的共享访问?

时间:2013-03-09 23:53:45

标签: c++ pointers set immutability shared-ptr

为了使讨论清楚,我将以非常一般的方式描述问题,即我既不会提供真实类的名称,也不会描述域/上下文(但是,如果它转向,我可能会紧急)。

想象一下班级A。让这个类有2个不可变的字段,例如xy(请注意,这些可能是对象,即效率低下复印)。另外,让这些xy主要字段,即只有它们用于== / !=运算符的实现以及哈希计算功能。

由于A在<{1}}和x方面属于不可变,因此我们的想法是让多个y实例(比如{{ 1}}和Aa1 a2(即a1.x == a2.x隐式拥有对这些a1.y == a2.y的共享访问权限a1 == a2,因此没有不必要的重复。

此外,现在假设x中有另一个字段:y次要可变,并用作排序行为调整A。根据设计,希望在z 相等实例之间共享此字段。因此,如果我调用A,此更改也会影响A,因为他们对a1.setZ(...)的访问权限是共享

因此,我们最终得到一个具有纯值语义的类a2,但是在相同的实例中共享其成员隐式。 AFAIK这种模式称为Flyweight别名

在我们提出问题之前还有一个细节。项目中的大多数类都是使用 Pimpl 成语:

实现的
z

和类A不是排除。这就是为什么提出的实施上述方案的想法如下:

  1. 共享指针用于private: class Private; Private* p; 而非原始用户 Pimpl 成语;
  2. 将全局共享指针设置为A;
  3. A::Private的构造函数中检查是否共享 指针到合适的A::Private已存在于集合中 (当然,使用AA::Private),如果是,则只需设置x 它,否则创建y的新实例并存储 此集合中的共享指针,同样将p设置为它;
  4. A::Private的析构函数应该从集合中删除共享指针p
  5. 这看起来像是最简单直观的实现。但问题是,由于此全局集将共享指针保存到A::Private,这意味着即使相应的this的所有实例都被销毁,引用计数器也将保留在A::Private上,即它永远不会到达A,因此永远不会释放内存。

    我认为如果某些共享指针提供一种方法来设置引用计数器的下限,那将是一件好事。在这种情况下,例如,我只需将其设置为1,这意味着当它达到0时,它会释放内存。不幸的是,我还没有在流行的库(Boost,Qt,Poco等)中找到任何这种行为的实现。当然,我可以为我的问题做手动引用计数,但这感觉不对,闻起来像重新发明轮子。

    可能还有其他方法可以解决这个问题。期待您的建议。

    注意:我想立即拦截任何建议将问题转换为指针语义,我很清楚。我完全需要上述方案的解决方案。

2 个答案:

答案 0 :(得分:2)

如果我正确理解了你的设计问题,那么我会让全局集包含,非拥有指针(例如weak_ptr<>),它们能够检查它们是否是悬空,但他们不会增加参考数量。

std::vector<std::weak_ptr<Private>> _objects;

因此,当所有拥有对象的共享指针被销毁时,该对象也将被销毁**。

现在你的全局集将留下悬空weak_ptr<>,但好处是你可以检查该指针是否指向一个活着的对象(使用lock()成员函数获取可能为空的shared_ptr<>。如果没有,则不会取消引用它:

// A simple, hypothetical loop through the collection of objects
// which does something, but checks whether the pointers are
// dangling before doing that something on a possibly dead object
// that would be Undefined Behavior)
std::for_each(_objects.begin(), _objecs.end(), [] (std::weak_ptr<Private> p) 
{
    std::shared_ptr<Private> sp = p.lock();
    if (sp != nullptr)
    {
        sp->callMember(); // For instance...
    }
});

如果您还想在对象被销毁后从集合中删除对应的weak_ptr<>,那么您可以使用自定义删除器例程。当对象被销毁时将调用您的例程,并将指针传递给该对象:此时,在取消分配之前,您可以从集合中擦除相应的元素。

例如,一个实例化A类型的新对象并向其返回shared_ptr的函数可能看起来像这样:

static std::shared_ptr<object> make_A()
{
    std::shared_ptr<Private> sp(
        new Private(),   // Instantiate the object
        [] (Private* p)  // Set up the custom deleter...
        {
            // Remove the corresponding element from the vector...
            _objects.erase(
                // ...so let's find that element!
                std::find_if( 
                    _objects.begin(),
                    _objects.end(),
                    [p] (std::weak_ptr<priv> wp)
                    {
                        // lock() will return a null pointer if wp is dangling
                        std::shared_ptr<priv> sp = wp.lock();

                        // In case wp is not dangling, return true if and only
                        // if it points to the object we're about to delete
                        return ((sp != nullptr) && (sp.get() == p));
                    })
                );
        });
}

在这里我假设C ++ 11,您可以在C ++ 03中轻松地执行相同的操作,将std::shared_ptr<>替换为boost::shared_ptr<>,将std::weak_ptr<>替换为boost::weak_ptr<>,并将lambdas替换为lambdas使用正确定义的仿函数。

希望这有帮助。

答案 1 :(得分:0)

您检查了Boost.Flyweight吗?