C++智能指针与更改指针值一起使用

C++ use Smart Pointers with changing pointer values

本文关键字:指针 一起 智能 C++      更新时间:2023-10-16

考虑一个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

但是myfoo2.get()调用似乎很笨拙。我是否按预期使用它?

它不是黑客,您可以按预期使用它。

我会更进一步,将整体包装在一个类中:

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;
};
<</div> div class="answers">

必须编写.get()是使用智能指针的不幸后果,但我认为如果您想传递给接受非拥有的、可为空的指针的函数,这是最佳实践。

但是,在实践中,我经常发现您不需要它为空,并且可以接受引用而不是原始指针。然后语法就不那么"黑客"了:

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);
}

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

我会亲自创建一个基于模板的包装器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;
}