С++ использует интеллектуальные указатели с изменяющимися значениями указателя
Рассмотрим библиотеку C, которая определяет функции для создания, уничтожения и работы с настраиваемой структурой
struct Foo;
void foo_action(Foo*);
Foo* foo_create();
void foo_free(Foo*);
В настоящее время я использовал библиотеку в своем проекте на С++ следующим образом
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()
кажется взломанным. Использую ли я его как предназначение?
Там другая часть библиотеки, которая создает и работает с какой-то структурой списка. Апи выглядит как
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
вызывается в текущем значении указателя при освобождении экземпляра указателя?
Ответы
Ответ 1
но вызов 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;
};
Ответ 2
Наличие записи .get()
является неудачным следствием использования интеллектуальных указателей, но я думаю, что лучше всего использовать, если вы хотите перейти к функции, которая принимает указатель, не имеющий прав, с нулевым значением.
Но на практике я часто нахожу, что вам не нужно, чтобы он был нулевым и мог принимать ссылку вместо raw-pointer. Тогда синтаксис немного менее "взломан":
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()
неуклюж, не хаки.
Я лично создал обертку на основе шаблона obj_ptr
(вы выберете более релевантное имя) и примените черты для каждого типа объекта, чтобы смоделировать ваше требование на С++. Затем оболочка может удалить неудобство доступа к базовому объекту.
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;
}