C ++ copy-safe ressource deallocation

时间:2013-12-31 21:31:06

标签: c++ raii

我有一些小类,包括对外部资源的引用。因为它们很小但经常传递并经常堆栈分配,所以我不传递指针而是实例本身。因此通常使用复制构造函数,有时使用赋值。

然而,这会导致资源管理不平衡,一旦第一个实例离开某个范围,外部资源就会被释放,而仍有副本。

对于指针,有一些聪明但通常很昂贵的东西,如std::shared_ptr

但是,如何处理实例传递情况?似乎几乎不可能通过一个实例知道它是否是最后一种被破坏的?

2 个答案:

答案 0 :(得分:0)

以前我曾推荐过一个单身人士,但对使用这种模式有相当多的批评 - one example

如果你想要更具异国情调的东西(也许你需要在你的课堂上存储某种本地状态),你可以在对象本身中嵌入你自己的引用计数:

// Psuedo code
class Resource
{
    static unsigned _refCount;

public:
    Resource() {++_refCount;}
    ~Resource() {--_refCount;  if(_refCount == 0) // cleanup!;}
};

您会注意到这与shared_ptr基本上是相同的方案,但是它允许通过实例化而不是传递指针来使用堆栈上的项目(您还必须将资源存储在静态成员中,对于所有实例有权访问它)。正如其中一条评论建议的那样,您应该更喜欢shared_ptr,如果您可以通过个人资料验证它对您的需求来说太慢,则只能拒绝它。

答案 1 :(得分:0)

这个问题的答案取决于你想要达到的终身政策。

如果你想要引用计数,那么当最后一个句柄超出范围时会释放资源,那么shared_ptr和intrusive_ptr是我所知道的唯一选项。你可以手动输入一些东西,但在做这件事之前我会想很久很久。如果您的程序是单线程的,那么最简单的方法是使用Boost共享指针禁用原子计数:

#define BOOST_SP_DISABLE_THREADS
#include <boost/shared_ptr.hpp>

这应该会显着降低成本。当然上面的评论是正确的,在走这条路线之前,你通常会对代码进行分析,以确保它确实是一个瓶颈,但是这个讨论与你提出的问题有点偏离。

如果您可以使用静态缓存而不是引用计数,那么您可以使用静态缓存:

// In the header.
#include <memory>
#include <functional>

namespace detail {
  using deleter_t = std::function<void (void *)>;
  using  handle_t = std::unique_ptr<void, deleter_t>;

  void *search_persistent_cache(std::string const &id);
  void  add_to_persistent_cache(std::string const &id, void *);
}

template<typename T, typename... Args>
T &get_persistent_resource(std::string const &id, Args&&... args) {
  auto resource = search_persistent_cache(id);

  if(resource) {
    return *static_cast<T*>(resource);
  }

  auto uniq_instance = std::unique_ptr<T>(std::forward<Args>(args)...);
  auto  raw_instance = uniq_instance.get();

  detail::deleter_t deleter = [](void *resource) -> void {
    delete static_cast<T*>(resource); 
  };

  try {
    add_to_persistent_cache(id,
        detail::handle_t { uniq_instance.release(), std::move(deleter); });
  } catch(...) {
    delete instance;
    return nullptr;
  }
}

这对于一般的想法应该足够了。我将由您来实现源文件中的静态存储和未定义函数。您可以进行一些调整,例如,如果您不需要由标识符索引的资源,或者您希望避免使用void *,那么调试版本可以使用boost :: polymorphic_down_cast。

我无法想到任何其他生命周期管理策略并不比上面显示的复杂得多。