C++ 使用具有更改指针值的智能指针

C++ use Smart Pointers with changing pointer values

考虑定义用于创建、销毁和使用自定义结构的函数的 C 库

struct Foo;
void foo_action(Foo*);
Foo* foo_create();
void foo_free(Foo*);

目前,我在我的C++项目中使用的库如下

Foo* myfoo = foo_create();
foo_action(myfoo);
foo_free(myfoo);

我理解为什么智能指针很重要并且想迁移我的代码以使用它们。 这就是代码现在的样子。

#include <memory>
#include <functional>
typedef std::unique_ptr<Foo, std::function<void(Foo*)>> FooPtr;
// ...
FooPtr myfoo2(foo_create(), foo_free);
foo_action(myfoo2.get());

它似乎有效,但 myfoo2.get() 调用似乎很老套。 我是否按预期使用它?

库的另一部分创建并使用某种列表结构。 api 看起来像

struct Bar;
Bar* bar_append(Bar*, int);
void bar_free_recursive(Bar*);

并用作

// using NULL as current Bar* creates the initial structure
Bar* bar = bar_append(NULL, 1);
// each invocation leads to another 'head' structure
bar = bar_append(bar, 42);
bar = bar_append(bar, 123);

由于指针(指向的地址)随着每次bar_append调用而变化,我将如何在此处引入智能指针,以便在指针实例时对当前指针值调用bar_free_recursive被释放了吗?

but the myfoo2.get() invocation seems hacky. Am I using it as intended?

这不是 hacky,您可以按预期使用它。

我会更进一步,将整个包裹在 class:

struct Foo;
void foo_action(Foo*);
Foo* foo_create();
void foo_free(Foo*);

class FooWrapper
{
public:
    FooWrapper() : mFoo(foo_create()) {}

    void action() { foo_action(mFoo.get()); }
private:
    struct FooDeleter
    {
        void operator()(Foo* foo) const { foo_free(foo); }
    };

    std::unique_ptr<Foo, FooDeleter> mFoo;
};

同理:

struct Bar;
Bar* bar_append(Bar*, int);
void bar_free_recursive(Bar*);

class BarWrapper
{
public:
    explicit BarWrapper(int n) : mBar(bar_append(nullptr, n)) {}

    void append(int n) { mBar.reset(bar_append(mBar.release(), n)); }

private:
    struct BarDeleter
    {
        void operator()(Bar* bar) const { bar_free_recursive(bar); }
    };

    std::unique_ptr<Bar, BarDeleter> mBar;
};

我会说 myfoo2.get()笨拙,而不是 hacky

我会亲自创建一个基于模板的包装器 obj_ptr(您可以选择一个更相关的名称)并使用每种类型的对象的特征以 C++ 方式为您的需求建模。然后包装器可以消除访问底层对象的笨拙。

template <typename T, typename Traits>
class obj_ptr final
{
    std::unique_ptr<Foo, void(*)(T*)> ptr_{ Traits::create(), Traits::free };

public:
    operator T*() { return ptr_.get(); }

    operator const T*() const { return ptr_.get(); }

    T* operator->() { return ptr_.get(); }

    const T* operator->() const { return ptr_.get(); }
};

class foo_traits
{
public:
    static Foo* create() { return foo_create(); }

    static void free(Foo* foo) { foo_free(foo); }
};

int main()
{
    using FooPtr2 = obj_ptr<Foo, foo_traits>;

    FooPtr2 myfoo2;

    foo_action(myfoo2);

    return EXIT_SUCCESS;
}

不得不写 .get() 是使用智能指针的一个不幸结果,但我认为最好的做法是如果你想传递给一个接受非拥有的、可为空的指针的函数。

但是,在实践中我经常发现您不需要它可以为空并且可以接受引用而不是原始指针。然后语法有点少 "hacky":

void foo_action(Foo&);  // accept a reference instead of a raw-pointer

struct FooDeleter {
    void operator()(Foo* foo) const { foo_free(foo); }
};

using FooPtr = std::unique_ptr<Foo, FooDeleter>;

FooPtr make_foo() {
  return FooPtr(foo_create());
}

int main() {
    auto foo = make_foo();

    // ...  

    if (foo) {             // check for null
        foo_action(*foo);  // dereference smart-pointer
    } 
}

bar_append 应该与 unique_ptr 一起使用,前提是您使用 std::move:

struct BarDeleter {
    void operator()(Bar* bar) const { bar_free_recursive(bar); }
};

using BarPtr = std::unique_ptr<Bar, BarDeleter>;

BarPtr bar_append(BarPtr bar, int value) {
    return BarPtr(bar_append(bar.release(), value));
}

int main() {      
  BarPtr bar;
  bar = bar_append(std::move(bar), 42);
  bar = bar_append(std::move(bar), 123);
}