Note that in a real program you may want to check and act upon the return value of fclose
, which could be awkward from within a destructor: you don't get to return a value and throwing exceptions from destructors is a bad idea. Similar considerations may or may not apply for other types of pointer.
With that caveat out of the way, an alternative approach would be to specify the deleter as a functor:
struct file_deleter {
void operator()(std::FILE* fp) { std::fclose(fp); }
};
using unique_file = std::unique_ptr<std::FILE, file_deleter>;
The type alias allows you to simply write:
unique_file f{ std::fopen("file.txt", "rt") };
This is more ergonomic than having to pass an additional pointer or a lambda every time you create a pointer. The use of a functor type also means that the unique_ptr does not have to carry around a separate pointer for the deleter, which allows for space savings relative to the other approaches. To see this, I use the following code:
int main()
{
std::unique_ptr<FILE, decltype(&fclose)> f1{ nullptr, &fclose };
std::unique_ptr<std::FILE, void(*)(std::FILE*)> f2{
nullptr, [](std::FILE* p) { std::fclose(p); } };
unique_file f3{ nullptr };
std::FILE* f4{ nullptr };
std::cout << "sizeof(f1) = " << sizeof(f1) << '\n';
std::cout << "sizeof(f2) = " << sizeof(f2) << '\n';
std::cout << "sizeof(f3) = " << sizeof(f3) << '\n';
std::cout << "sizeof(f4) = " << sizeof(f4) << '\n';
}
Using MSVC building for an x64 target, I get the following output:
sizeof(f1) = 16
sizeof(f2) = 16
sizeof(f3) = 8
sizeof(f4) = 8
In this specific implementation, for the case using the functor the unique_ptr
is the same size as a raw pointer, which is not possible for the other approaches.