【问题标题】:C++ use Smart Pointers with changing pointer valuesC++ 使用智能指针来改变指针值
【发布时间】:2017-10-30 12:36:50
【问题描述】:

考虑一个定义用于创建、销毁和使用自定义结构的函数的 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

【问题讨论】:

  • 记住你可以重载函数。为什么不创建一个简单的内联重载 foo_action 函数,该函数接受 FooPtr 参数,并使用原始非智能指针调用旧的 foo_action 函数?您仍然可以保留旧代码,同时使界面更美观。
  • @Someprogrammerdude 好点,谢谢。我只是不确定是否需要它,或者是否有更直接的转换方式。
  • 使用std::function作为你的deleter会比制作一个特定的deleter效率低。
  • @Someprogrammerdude 我不认为foo_action 应该特定于某种特定类型的智能指针,如果它没有获得所有权。我怀疑foo_action 可能应该采用Foo&amp;,然后调用代码只是*myfoo2,无论指针类型如何。
  • 似乎没有人解释为什么使用自己的删除器比使用std::function 或函数指针更有效。这是因为使用std::function 或者函数指针需要std::unique_ptr 来存储删除器;它是在运行时设置的。所以sizeof(std::unique_ptr&lt;T&gt;) 将不再是T*。此外,std::unique_ptr 的神奇特性之一是编译器通常能够优化它。编译器将更难内联std::function 或函数指针。

标签: c++ smart-pointers


【解决方案1】:

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

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

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

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

【讨论】:

  • 额外的好处:如果您遵循正常的“仅在标题中声明的成员函数”样式,您只需要在FooWrapper.cpp#include &lt;Bar.h&gt; 中添加#include &lt;Foo.h&gt;#include &lt;Bar.h&gt;
  • 如果你已经用类包装了它,为什么要使用唯一指针?只需创建一个调用 foo_free 的析构函数
  • @DavidHaim:5/3/0 规则。根据您的建议,我将不得不编写析构函数,移动构造函数/赋值。
  • 我自己会在append 中拆分重置/发布代码。我太担心异常和评估顺序的混合会导致奇怪的极端情况使该代码无效。太多人通过在同一行读取和写入变量来创建 UB,让我有信心处理这些行,无论是读取还是写入。
  • 注意,在 C++17 中,你可以写成template&lt;auto x&gt; using value_t = std::integral_constant&lt; std::decay_t&lt;decltype(x)&gt;, x &gt;;,然后BarDeleter 就是value_t&lt;bar_free_recursive&gt;FooDeleter 就是value_t&lt;foo_free&gt;
【解决方案2】:

不得不写.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);
}

【讨论】:

    【解决方案3】:

    我会说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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-29
      • 1970-01-01
      • 2019-06-02
      相关资源
      最近更新 更多