Как использовать shared_ptr с указателем на структуру, которая не должна быть освобождена

В настоящее время я использую некоторые функции из библиотеки glib. С glib также появляется gio. glib - это библиотека C, поэтому мне нужно удалить некоторые структуры, которые я создаю.

для многих объектов я создаю smartpointer, например:

std::shared_ptr<GAsyncQueue> my_queue = std::shared_ptr<GAsyncQueue>(g_async_queue_create(), g_async_queue_unref);

Для этого создается общий указатель на GAsyncQueue, и это безопасно уничтожает очередь в конце ее жизни.

Однако я сталкиваюсь с проблемой, когда получаю указатель из библиотеки gio, который я не должен освобождать. В следующем коде my_connection есть GSocketClient, который реализует (в glib-talk) GIOStream.

std::shared_ptr<GInputStream> my_input_stream = 
     std::shared_ptr<GInputStream> (
        g_io_stream_get_input_stream(G_IO_STREAM(my_connection.get()))
     );

Поскольку в документации по GIOStream упоминается, что указатель, полученный с помощью g_io_stream_get_input_stream(), не должен быть освобожден. Это связано с тем, что он принадлежит экземпляру my_connection. Я думал о создании lamda для объекта destroy, второго параметра объекта общего указателя. например auto deleter = [](GInputStream* ptr) {};, а затем дать эту лямбду как функцию detroy для общего указателя, но это кажется глупым.

Ответы

Ответ 1

Ну, альтернатива no-op deleter может использовать общий указатель наложения aliasing

template <class U> shared_ptr (const shared_ptr<U>& x, element_type* p) noexcept;

Он разделяет x, но после get() вы вернетесь p.

Обсуждение: Что такое конструктор псевдонимов shared_ptr для?

Ответ 2

Вероятно, вам просто не нужен std::shared_ptr. И вам, вероятно, даже не нужен указатель.

Когда я прочитал ваш вопрос и комментарии, я не вижу смысла против

auto& my_input_stream = *( g_io_stream_get_input_stream(G_IO_STREAM(my_connection.get())) )

Верно, что указатели допускают дополнительные данные. Однако также верно, что он в основном использовался неправильно. Имея

void foo( type* ptr)
{
    if (!ptr)
        throw exception;
}

часто не имеет смысла. Если функция должна работать над конкретными данными, разрешение параметра NULL полезно, только если вы беспокоитесь о предоставлении этих данных. В противном случае просто требуется ссылка (возможно, const) на объект.

Умные указатели полезны; но они все еще указатели. Избежать их вообще, если это возможно, еще лучше.


Из комментариев:

Однако ссылка всегда должна быть инициализирована

Совершенно верно. Начиная с С++ 11, хотя у нас есть std::reference_wrapper, который также может быть повторно закреплен и сохранен в контейнерах.

Ответ 3

Вы можете использовать тип удаления, который ничего не делает, но его необходимо передать как аргумент конструктору shared_ptr

struct DoNothing {
    template <typename T>
    void operator()(T*) const noexcept { }
};

При создании shared_ptr вам нужно будет создать один из этих удалений и передать его в конструкторе (как вы это делаете с лямбдой). Вы можете сделать это проще на себе с помощью промежуточной функции

template <typename T>
std::shared_ptr<T> non_deleting_shared_ptr(T* ptr) {
    return {ptr, DoNothing};
}

auto my_input_stream = 
    non_deleting_shared_ptr(
        g_io_stream_get_input_stream(G_IO_STREAM(my_connection.get()));

Однако главный вопрос заключается в том, почему вы используете интеллектуальные указатели, когда вы не хотите, чтобы принадлежность к нему была частью. Вам почти наверняка будет лучше всего с GAsyncQueue*, если, конечно, вы не находитесь в ситуации, когда у вас есть shared_ptr, который иногда нужно освобождать. Как член данных, возможно?