shared_ptr и FILE для упаковки cstdio (обновление: также dlfcn.h)

Даже при наличии <fstream> может быть причина для использования интерфейса файла <cstdio>. Мне было интересно, будет ли упаковка FILE* в shared_ptr полезной конструкцией или если у нее есть какие-либо опасные ошибки:

#include <cstdio>
#include <memory>

std::shared_ptr<std::FILE> make_file(const char * filename, const char * flags)
{
  std::FILE * const fp = std::fopen(filename, flags);
  return fp ? std::shared_ptr<std::FILE>(fp, std::fclose) : std::shared_ptr<std::FILE>();
}

int main()
{
  auto fp = make_file("hello.txt", "wb");
  fprintf(fp.get(), "Hello world.");
}

Обновление: Я просто понял, что not разрешено fclose нулевой указатель. Я изменил make_file соответственно, чтобы в случае сбоя не было специального дебетера.


Второе обновление: Я также понял, что unique_ptr может быть более подходящим, чем shared_ptr. Вот более общий подход:

typedef std::unique_ptr<std::FILE, int (*)(std::FILE *)> unique_file_ptr;
typedef std::shared_ptr<std::FILE> shared_file_ptr;

static shared_file_ptr make_shared_file(const char * filename, const char * flags)
{
  std::FILE * const fp = std::fopen(filename, flags);
  return fp ? shared_file_ptr(fp, std::fclose) : shared_file_ptr();
}

static unique_file_ptr make_file(const char * filename, const char * flags)
{
  return unique_file_ptr(std::fopen(filename, flags), std::fclose);
}

Изменить. В отличие от shared_ptr, unique_ptr только вызывает дебетер, если указатель не равен нулю, поэтому мы можем упростить реализацию make_file.

Третье обновление: . Можно создать общий указатель из уникального указателя:

make_file

Четвертое обновление: Аналогичная конструкция может использоваться для unique_file_ptr up = make_file("thefile.txt", "r"); shared_file_ptr fp(up ? std::move(up) : nullptr); // don't forget to check /dlopen():

dlclose()
42 голоса | спросил Kerrek SB 8 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowThu, 08 Sep 2011 17:30:33 +0400 2011, 17:30:33

3 ответа


19

Честно говоря, я очень много думал, чтобы придумать какой-либо реальный недостаток, который мог бы иметь, но я ничего не могу придумать. Конечно, выглядит странно, чтобы сместить структуру C в shared_ptr, но пользовательский делектор позаботится об этой проблеме, так что это просто субъективная неприязнь и только поначалу. На самом деле сейчас, я думаю, это довольно умно.

ответил ultimA 11 +04002011-10-11T19:24:04+04:00312011bEurope/MoscowTue, 11 Oct 2011 19:24:04 +0400 2011, 19:24:04
22

Я должен начать с того, что я не совсем согласен с распространенным убеждением, что «явное лучше, чем неявное». Я думаю, в этом случае, вероятно, по крайней мере так же хорошо иметь класс, который просто неявно преобразуется в нужный тип:

class file { 
    typedef FILE *ptr;

    ptr wrapped_file;
public:
    file(std::string const &name, std::string const &mode = std::string("r")) : 
        wrapped_file(fopen(name.c_str(), mode.c_str()))    
    { }

    operator ptr() const { return wrapped_file; }

    ~file() { if (wrapped_file) fclose(wrapped_file); }
};

Я не пытался сделать это движимым, но эта же общая идея применима, если бы вы это сделали. Это (помимо всего прочего) преимущество, заключающееся в том, что вы работаете с file напрямую, как файл, вместо того, чтобы иметь уродливый (и в основном бессмысленный) .get() бородавок, поэтому код будет примерно таким:

file f("myfile.txt", "w");

if (!f) {
   fprintf(stderr, "Unable to open file\n");
   return 0;
}

fprintf(f, "Hello world");

Это имеет несколько преимуществ. Вышеупомянутая чистота является довольно важной. Другим является тот факт, что пользователь теперь имеет довольно обычный тип объекта, поэтому, если они хотят использовать перегрузку примерно так же, как и с ostream, это тоже довольно просто:

file &operator<<(file &f, my_type const &data) { 
    return data.write(f);
}

// ...

file f("whatever", "w");
f << someObject;

Короче говоря, если пользователь хочет делать ввод /вывод C-стиля, это работает нормально. Если он предпочитает делать I /O больше, чем использование iostreams, многие из них довольно легко поддерживать. Большая часть из них по-прежнему остается синтаксическим сахаром, поэтому, как правило, он не будет налагать никаких накладных расходов на сравнение с FILE *.

ответил Jerry Coffin 17 42011vEurope/Moscow11bEurope/MoscowThu, 17 Nov 2011 19:58:15 +0400 2011, 19:58:15
4

Использование указателя функции справочника функций в качестве делетера имеет два основных недостатка:

  1. Делетер имеет состояние и, следовательно, требует хранения, даже если он всегда один и тот же, и функция создателя искусственно используется для создания этого «постоянного» состояния, даже если выбора нет сделать здесь.

  2. Учет адреса стандартной библиотеки очень проблематичен. C ++ 20 начал откровенно объявлять вне закона эту практику, и она обычно создает хрупкий код. Функции в первую очередь предназначены для называемых определенным образом. Детали того, что функция имеет перегрузки, аргументы по умолчанию и т. Д., Как правило, не предназначены для наблюдения и могут изменяться по прихоти исполнителя. Поэтому стандартные библиотечные функции всегда должны быть вызваны .

Объединение этих двух наблюдений немедленно приводит к улучшению решения. Определите свой собственный класс делетера. Этот класс может быть конструктивным по умолчанию, что делает конструкцию интеллектуального указателя прямолинейной.

Пример (с помощью dlopen /dlclose):

struct DlCloser
{
  operator()(void * dlhandle) const noexcept { dlclose(dlhandle); }
};

using dl_ptr = std::unique_ptr<void, DlCloser>;

dl_ptr make_loaded_dso(const string & filename)
{
  return dl_ptr(dlopen(filename.c_str()));
}

Обратите внимание, что функция-создатель теперь почти бесполезна; Я мог бы просто написать dl_ptr p(dlopen(filename)) вместо auto p = make_loaded_dso(filename.c_str()).

Наконец, вот небольшой отрывок от lambdas: обычный способ использовать библиотечные функции как обратные вызовы и соблюдать вышеупомянутый интерфейс «только для вызова» - использовать лямбда-выражение, например [](void * h) { dlclose(h); }. Тем не менее, лямбда-выражения не подходят для хороших типов отходов. Несмотря на то, что C ++ 20 создал безстоящие lambdas default-construcible и разрешил лямбды появляться в неоцененных контекстах, мы обычно не можем использовать что-то вроде

std::unique_ptr<void, decltype([](void * h) { dlclose(h); })>

в качестве типа библиотеки, поскольку (если выше содержится в файле заголовка), выражение лямбда имеет уникальный тип в каждой единицы перевода, и поэтому у нас бы были нарушения ODR. Неопубликованные и построенные по умолчанию lambdas полезны только в локальных настройках, но не для библиотек и типов интерфейсов.

ответил Kerrek SB 2 PMpMon, 02 Apr 2018 23:52:22 +030052Monday 2018, 23:52:22

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132