<fstream>
,也可能有理由使用<cstdio>
文件界面。我想知道将FILE*
包裹到shared_ptr
中是否有用,或者它是否有任何危险陷阱:允许fclose
为空指针。我相应地修改了make_file
,以便在出现故障时不会有特殊的删除器。这是一种更通用的方法:#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.");
}
编辑。与
unique_ptr
不同,shared_ptr
仅在指针非零时才调用删除程序,因此我们可以简化shared_ptr
的实现。 br /> 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);
}
第四次更新:
unique_ptr
/ make_file
可以使用类似的构造: >#1 楼
老实说,我一直在努力想想这可能带来的任何实际缺点,但我什么也没想。将C结构包装到shared_ptr中看起来确实很奇怪,但是自定义删除器解决了这个问题,因此这只是主观上的不满,而且只是在最初。实际上,我现在认为它很聪明。#2 楼
我应该从一个事实开始,即我并不完全同意“明确胜于隐性”这一普遍观念。我认为在这种情况下,拥有一个隐式转换为正确类型的类可能至少同样好:可移动的,但如果您这样做的话,同样的一般想法也适用。这具有(除其他优点外)优点是您可以直接将
file
作为文件使用,而不是使用丑陋的(且几乎没有意义的).get()
疣,因此代码类似于:br />
这有两个优点。上述清洁度是相当重要的。另一个事实是用户现在具有相当普通的对象类型,因此,如果他们想要像使用ostream一样大致使用重载,那也很容易:
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); }
};
简而言之,如果用户想执行C风格的I / O,那很好。如果他/她更喜欢像iostreams一样使用I / O,那么其中很多也很容易支持。但是,大多数仍然只是语法糖,因此与直接使用
FILE *
相比,它通常不会产生任何开销。评论
\ $ \ begingroup \ $
这是一个不错的设计。谢谢! (但是,运算符ptr()应该为const吗?)
\ $ \ endgroup \ $
– Kerrek SB
2011年11月17日在16:04
\ $ \ begingroup \ $
@KerrekSB:是的,可能。
\ $ \ endgroup \ $
–杰里·科芬(Jerry Coffin)
2011年11月17日在16:08
\ $ \ begingroup \ $
由于最初的答案非常费力地正确管理FILE *的生存期,因此提出一种无法正确处理复制的解决方案似乎很奇怪。我可能会在OP的unique_file_ptr或shared_file_ptr之上构建类似您的文件的东西,而不是代替它们。
\ $ \ endgroup \ $
–乔纳森·韦克利
2014年7月9日在12:38
\ $ \ begingroup \ $
不应该重载运算符<<是包含iostream对象作为第一个参数并返回值(通过引用),以及类文件对象作为第二个参数的东西吗?
\ $ \ endgroup \ $
–子字
16-2-6在11:41
\ $ \ begingroup \ $
@simplicisveritatis:嗯...什么?不,这里的想法是文件类型基本上可以代替iostream,因此使用它不涉及iostream。
\ $ \ endgroup \ $
–杰里·科芬(Jerry Coffin)
16年6月6日在16:58
#3 楼
使用函数引用的函数指针作为删除器有两个主要缺点:删除器具有状态,因此需要存储,即使它始终相同,并且maker函数是人为地即使无法在此处进行选择,它通常也用于创建此“恒定”状态。
获取标准库函数的地址存在很大问题。 C ++ 20已开始彻底禁止这种做法,并且通常会创建易碎的代码。首先,函数被设计为以某种方式调用。函数是否具有重载,默认参数等的详细信息通常并不旨在被观察,而是易于在实现者的追捧下进行更改。因此,应始终调用标准库函数。
将这两个观察值放在一起会立即产生改进的解决方案:定义您自己的自定义删除器类。此类可以是默认可构造的,从而使智能指针的构造变得简单明了。示例(使用
dlopen
/ dlclose
):struct DlCloser
{
void 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()));
}
请注意,现在maker函数几乎没有用。我不妨只写
dl_ptr p(dlopen(filename))
而不是auto p = make_loaded_dso(filename.c_str())
。最后,在lambda上还有一点点:使用库函数作为回调并遵守上述“仅调用”接口的常用方法是使用lambda表达式,例如[](void * h) { dlclose(h); }
。但是,lambda表达式不能提供良好的删除器类型。即使C ++ 20使无状态的lambda成为默认可构造的并且允许lambda在未评估的上下文中出现,但是我们通常不能将std::unique_ptr<void, decltype([](void * h) { dlclose(h); })>
作为库类型使用,因为(如果以上内容包含在头文件中)lambda表达式在每个翻译单元中都有唯一的类型,因此我们将违反ODR。未评估且默认可构造的lambda仅在本地设置中有用,而对于库和接口类型则无用。
评论
\ $ \ begingroup \ $
内联应该解决ODR违规的最后一个问题
\ $ \ endgroup \ $
–OwnageIsMagic
20-3-12在14:26
\ $ \ begingroup \ $
或静态自动删除器= [](void * h){dlclose(h); };使用ptr = std :: unique_ptr
\ $ \ endgroup \ $
–OwnageIsMagic
20 Mar 12 '20 at 14:29
评论
坦率地说,我有几个程序可以剖析二进制文件,因此我经常想打印出一些以固定宽度的十六进制表示的数据块,以十进制表示的其他数据块,以浮点数打印的其他数据块,对于printf这个目的。尝试在iostream中这样做会导致大量的样板代码,而且还不清楚是小数还是十六进制。所以fprintf是:-)但是我总的来说有点好奇,这是否是有用且正确的习惯用法。将unique_ptr不是一个更好的选择吗?您真的要分享吗?
不能分享它很酷吗?是的,unique_ptr当然是另一种选择...我刚刚想到了另一个应用程序:您可以将这些人放入标准容器中,从而轻松管理打开文件的集合。
我发现unique_ptr更好,因为它仅在指针不为null时才调用deleter。同样,您可以从唯一的指针创建共享指针,但这带来了删除空指针的问题。
请不要更新您问题中的代码以合并答案的反馈,因为这样做有悖于“代码审阅”的“问题+答案”风格。这不是一个论坛,您应该在其中保留问题的最新版本。收到答案后,请查看您可能会做什么和可能不会做什么。发布一个后续问题,旧问题和新问题都相互链接。