考虑一个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
?
答案 0 :(得分:21)
但是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;
};
答案 1 :(得分:5)
必须编写.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);
}
答案 2 :(得分:4)
我会说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;
}