【问题标题】:When using "new" to initialize unique_ptr<FILE*,File::Close> is the custom deleter responsible for freeing that memory?使用“new”初始化 unique_ptr<FILE*,File::Close> 时,自定义删除器是否负责释放该内存?
【发布时间】:2020-02-22 05:32:14
【问题描述】:

智能指针对我来说是一个新概念。我一直在尝试使用带有自定义删除器(unique_ptr)的智能指针围绕 fopen_s 和 fclose 包装一个 File 类。

以下是我的尝试。它成功编译、运行并生成了一个名为“text.txt”的文件,其内容为“Hello World”。

我不得不在我的 Open 函数中使用“new”来初始化 unique_ptr,因为 make_unique 似乎不适用于自定义删除器。由于我使用的是“新”,因此我的自定义删除器负责释放分配的内存吗?

我已经完成了我的程序 (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;
}

【问题讨论】:

  • 哇,这很复杂。而s_handle_map_ 似乎除了创建悬空手柄之外没有其他用途。
  • 为什么要用new分配一些东西?文件句柄是打开和关闭的,而不是分配和释放的。
  • 由于您手动且显式地使用delete,您未能将资源清理委托给智能指针。
  • Windows 有一个HANDLE 类型,但Handle 是什么? std::move(handle) 可能不会像你认为的那样做
  • @rustyx fclose() 是关闭和释放 FILE 结构的正确方法

标签: c++ c++11 unique-ptr


【解决方案1】:

每当您想到unique_ptr&lt;FILE*, ...&gt; 时,深吸一口气,稍等片刻,然后继续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;
}  

而且您根本不需要担心内存管理。

现在,概括您的问题:

  • 如果您基于new T 指针自己创建unique_ptr&lt;T,D&gt;,自定义删除器D 将负责delete T。否则,您将泄漏内存 (example)。
  • 因此,更好的方法是继续使用默认删除器,并确保 T 的析构函数将清除或关闭任何需要的东西。
  • 一旦您选择默认删除器,最好选择 make_unique,它比新方法具有一些优势 (see here why)

【讨论】:

  • 在这个例子中,std::ofstream 会更好。
  • @RemyLebeau 这确实也是我的第一个想法。但我更喜欢更通用的fstream,因为 OP 的 File 类允许选择模式。
  • 这没有回答原来的问题。
【解决方案2】:

您正在使std::unique_ptr 的使用变得比它需要的更复杂。不要在unique_ptr 中存储FILE** 指针,而是存储FILE*。这就是fopen_s() 的输出,对FILE 的所有访问都是通过FILE* 而不是FILE** 完成的。当 1 级就足够时,您不需要 2 级间接。

试试这个:

#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;
}

【讨论】:

  • 是的,这是正确的解决方案。对 unique_ptr 文档的进一步审查使我有了这样的理解:unique_ptr 析构函数不会释放内存,而删除器会。析构函数调用get_deleter() 默认使用std::default_delete&lt;T&gt;::operator()(T* ptr) 来释放内存。因此,如果您提供自己的删除器,您将替换默认的删除器,并且现在负责自己释放内存。我最初的解决方案有一个不必要的间接层,所以fclose()delete 是必要的。此解决方案只需要调用fclose()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-16
  • 1970-01-01
  • 2018-01-02
  • 2019-07-10
  • 2020-12-04
  • 1970-01-01
相关资源
最近更新 更多