有没有一种惯用的方法可以返回一个可选拥有其值的指针?

4
我有一个函数,给定一个路径名,查找并返回指向相关值的指针。有时该值位于静态缓存中,有时会在运行时计算和创建。

因此,有时调用者需要拥有该对象并在读取后删除它,有时则不需要。我想知道,是否有一些方法可以包装这个指针,让调用者根据需要自动释放它?

我考虑可能可以使用unique_ptr,但删除器是类型的一部分,那么如何返回相同的类型,有时实际上会删除,有时则不会呢?


1
一个 shared_ptr 可以搞定这个问题吧?如果值在缓存中存在,那么调用者将得到指向该值的共享指针,当超出范围时不会删除任何内容;如果没有,则表示调用方实际上具有新创建值的所有权,并且当共享指针超出范围时删除它。 - JBL
可能会。但是静态缓存不使用shared_ptr,它存储值。我在考虑是否可以使用一个带有no-op删除器的shared_ptr来返回指向这些值的指针。对于动态计算的值,可以使用带有删除器的shared_ptr。 - Scott Langham
哦,我后来意识到如果缓存实际上不使用指针,那就不是那么简单了。是的,在这种情况下,这看起来像是一种可行的方法。而且对调用者完全透明。 - JBL
3个回答

4
因此,一种解决方案是在函数内部创建一个普通的std::shared_ptr来返回其值,并为存储在map中的值创建一个空删除器的另一个智能指针。
您可以查看此解决方案的实时示例
您可以看到这两种用例都不需要调用代码执行任何操作,并且完全透明。

你不需要一个空的删除器。只需将map也保存为shared_ptr,通过引用计数自动实现即可。 - DarkWanderer
@DarkWanderer,问题是,显然 OP 在他的地图中有 而不是指针。这本来会更容易(也是我的第一个想法),但似乎不可能。 - JBL

1
您可以使用 std::shared_ptr,但这并不能真正描述您的所有权模型。您是否考虑过编写自己的包装器,其中包含一个 std::unique_ptr 和一个原始指针,并根据情况使用正确的指针?类似这样的东西:
#include <cassert>
#include <memory>

class MyClass { };

class Wrapper {
  const MyClass*           cached_;
  std::unique_ptr<MyClass> owned_;
 public:
  Wrapper() : cached_(nullptr) {}
  void setCached(const MyClass* cached) {cached_ = cached;}
  void setOwned(std::unique_ptr<MyClass> owned) { owned_ = std::move(owned); }
  const MyClass* get() const {return cached_ ? cached_ : owned_.get();}
};

Wrapper getWrapper(int i) {
  static MyClass first;
  static MyClass second;

  Wrapper wrapper;

  if (i == 0)
    wrapper.setCached(&first);
  else if (i == 1)
    wrapper.setCached(&second);
  else
    wrapper.setOwned(std::unique_ptr<MyClass>(new MyClass()));

  return wrapper;
}

int main() {
  for (int i = 0; i != 4; ++i) {
    Wrapper wrapper = getWrapper(i);
    assert(wrapper.get() != nullptr);
  }
}

包装器可以将调用转发到真实类,也可以提供对真实类的原始指针的访问。
或者包装器可以以多态方式工作,具有接口和两个实现。一个使用原始指针,另一个使用唯一指针:
#include <cassert>
#include <memory>

class MyClass {};

class Wrapper {
 public:
  virtual ~Wrapper() = 0; 
  virtual const MyClass* get() const = 0;   
};

Wrapper::~Wrapper() {}

class OwnerWrapper : public Wrapper {
  std::unique_ptr<MyClass> owned_;
 public:
  OwnerWrapper(std::unique_ptr<MyClass> in) : owned_(std::move(in)) {}
  virtual const MyClass* get() const { return owned_.get(); }
};

class PtrWrapper : public Wrapper {
  const MyClass* ptr_;
 public:
  PtrWrapper(const MyClass* ptr) : ptr_(ptr) {}
  virtual const MyClass* get() const { return ptr_; }
};

std::unique_ptr<Wrapper> getWrapper(int i) {
  static MyClass first;
  static MyClass second;

  if (i == 0)
    return std::unique_ptr<Wrapper>(new PtrWrapper(&first));
  else if (i == 1)
    return std::unique_ptr<Wrapper>(new PtrWrapper(&second));
  else {
    std::unique_ptr<MyClass> myclass(new MyClass());
    return std::unique_ptr<Wrapper>(new OwnerWrapper(std::move(myclass)));
  }
}

int main() {
  for (int i = 0; i != 4; ++i) {
    auto wrapper = getWrapper(i);
    assert(wrapper->get() != nullptr);
  }
}

1
你可以使用具有知道是否释放的删除器的 std::unique_ptr。虽然删除器 类型unique_ptr 类型的一部分,但不同的 unique_ptr 实例可以有不同的删除器实例:
template <class T>
class delete_if_not_cached {
    bool cached;
public:
    delete_if_not_cached(bool c = false) : cached(c) {}
    void operator()(T *obj) { if (!cached) delete obj; }
}   

如果您将函数返回为std::unique_ptr<T, delete_if_not_cached<T>>,则可以将指针返回到缓存中,创建该指针的方法如下:

    return std::unique_ptr<T, delete_if_not_cached<T>>(raw_pointer, delete_if_not_cached<T>(true));

要返回一个非缓存对象,请使用:
    return std::unique_ptr<T, delete_if_not_cached<T>>(new T(...))

一个潜在的陷阱是,如果您从缓存中删除东西,则可能会留下您先前返回的悬空unique_ptr。如果这是一个问题,那么在缓存中使用shared_ptr来返回和使用可能更有意义。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接