При использовании «new» для инициализации unique_ptrответственный за удаление отвечает за освобождение памяти? - PullRequest
1 голос
/ 26 октября 2019

Умные указатели - это новая концепция для меня. Я пытался обернуть класс File вокруг fopen_s и fclose, используя умный указатель с пользовательским средством удаления (unique_ptr).

Ниже приведена моя попытка. Он успешно компилирует, запускает и генерирует файл с именем «text.txt» с содержимым «Hello World», как и ожидалось.

Мне пришлось использовать «new» для инициализации unique_ptr в моей функции Open, поскольку make_unique не делаетПоявится работа с пользовательскими удалителями. Так как я использую «new», отвечает ли мой пользовательский инструмент удаления за освобождение выделенной памяти?

Я прошел программу (VS2019). File :: Close вызывается только один раз. Я ожидал, что он будет вызван, когда «дескриптор» в моей функции File: Open вышел из области видимости, но это был не тот случай. На это поведение может повлиять вызов std :: move (). Не уверен, как исследовать дальше, что здесь происходит.

#include <Windows.h>
#include <memory>
#include <string>
#include <map>

class File
{

private:

//functors - custom deleter
  struct Close { void operator()(FILE** _handle); };

//type definitions
  typedef std::unique_ptr<FILE*,File::Close> Handle;
  typedef std::map<std::string,Handle> HandleMap;

//static members
  static Handle& Open(std::string _name, std::string _mode);
  static HandleMap s_handle_map_;

//variables
  Handle& handle_;
  std::string name_;

public:

//functions
  File(std::string _name, std::string _mode);
  void Write(std::string _message);

};

File::HandleMap File::s_handle_map_;

File::File(std::string _name, std::string _mode)
:handle_(Open(_name,_mode)),
 name_(_name)
{
}

File::Handle& File::Open(std::string _name, std::string _mode)
{
  bool exist = s_handle_map_.count(_name) > 0;

  if (!exist)
  {
    Handle handle(new FILE*(nullptr));

    //open new file
    fopen_s(
      handle.get(),
      _name.c_str(),
      _mode.c_str()
    );

    //transfer ownership of handle
    s_handle_map_.emplace(
      _name,
      std::move(handle)
    );

  }

  return s_handle_map_[_name];
}

void File::Close::operator()(FILE** _handle)
{
  fclose(*_handle);
  *_handle = nullptr;

  //necessary?
  delete _handle;
  _handle = nullptr;
}

void File::Write(std::string _message)
{
  fprintf(*handle_, _message.c_str());
}

int WINAPI WinMain(HINSTANCE _instance, HINSTANCE _previous, LPSTR _cmd, int _show)
{
  File file("test.txt","w");
  file.Write("Hello World\n");
  return 0;
}

Ответы [ 2 ]

5 голосов
/ 26 октября 2019

Всякий раз, когда вы думаете о unique_ptr<FILE*, ...>, сделайте глубокий вдох, подождите минуту, затем продолжайте с fstream.

Следующий код делает то же самое, ноопирается на проверенную и хорошо протестированную стандартную библиотеку C ++. У fstream есть все функции, которые вы ожидаете, включая автоматическое закрытие, когда они больше не нужны:

int WINAPI WinMain(HINSTANCE _instance, HINSTANCE _previous, LPSTR _cmd, int _show)
{
  fstream file("test.txt", fstream::out);
  file << "Hello World\n";
  return 0;
}  

И вам вообще не нужно беспокоиться об управлении памятью.

Теперь, обобщая вашивопрос:

  • если вы создадите unique_ptr<T,D> самостоятельно на основе указателя new T, пользовательское средство удаления D будет нести ответственность за delete T. Если вы этого не сделаете, вы потеряете память ( пример ).
  • Поэтому лучшим подходом было бы продолжать использовать средство удаления по умолчанию и убедиться, что деструктор T очистит или закроет все необходимое.
  • И как только вы выберете средство удаления по умолчанию, лучше всего будет выбрать make_unique, который имеет некоторые преимущества по сравнению с новым подходом (, посмотрите здесь, почему )
0 голосов
/ 26 октября 2019

Вы делаете использование std::unique_ptr более сложным, чем нужно. НЕ храните указатель FILE** внутри unique_ptr, вместо этого сохраняйте FILE*. Это то, что выводит fopen_s(), и весь доступ к FILE осуществляется через FILE*, а не FILE**. Вам не нужно 2 уровня косвенности, когда достаточно 1 уровня.

Попробуйте это:

#include <Windows.h>
#include <memory>
#include <string>
#include <map>

class File
{
private:

//functors - custom deleter
  struct Close { void operator()(FILE* f); };

//type definitions
  typedef std::unique_ptr<FILE,File::Close> Handle;
  typedef std::map<std::string,Handle> HandleMap;

//static members
  static Handle& Open(std::string _name, std::string _mode);
  static HandleMap s_handle_map_;

//variables
  Handle& handle_;
  std::string name_;

public:

//functions
  File(std::string _name, std::string _mode);
  void Write(std::string _message);

};
File::HandleMap File::s_handle_map_;

File::File(std::string _name, std::string _mode)
 : handle_(Open(_name,_mode)), name_(_name)
{
}

File::Handle& File::Open(std::string _name, std::string _mode)
{
  auto iter = s_handle_map_.find(_name);

  if (iter == s_handle_map_.end())
  {
    FILE *f = nullptr;

    //open new file
    if (fopen_s(&f, _name.c_str(), _mode.c_str()) != 0)
        throw std::runtime_error("cannot open file");

    //transfer ownership of handle
    iter = s_handle_map_.emplace(_name, Handle(f)).first;
  }

  return iter->second;
}

void File::Close::operator()(FILE* f)
{
  if (f)
    fclose(f);
}

void File::Write(std::string _message)
{
  fprintf(handle_.get(), "%s", _message.c_str());
}

int WINAPI WinMain(HINSTANCE _instance, HINSTANCE _previous, LPSTR _cmd, int _show)
{
  File file("test.txt", "w");
  file.Write("Hello World\n");
  return 0;
}
...