18

With shared_ptr you can use a custom deleter, like:

auto fp = shared_ptr<FILE>( fopen("file.txt", "rt"), &fclose );
fprintf( fp.get(), "hello\n" );

and this will remember to fclose the file regardless of how the function exits.
However, it seems a bit overkill to refcount a local variable, so I want to use unique_ptr:

auto fp = unique_ptr<FILE>( fopen("file.txt", "rt"), &fclose );

however, that does not compile.

Is this a defect? Is there a simple workaround? Im I missing something trivial?

sp2danny
  • 7,488
  • 3
  • 31
  • 53
  • [Maybe related](http://codereview.stackexchange.com/questions/4679/shared-ptr-and-file-for-wrapping-cstdio-update-also-dlfcn-h) – Kerrek SB Oct 14 '14 at 12:34
  • 2
    Beware of [the differences](http://cplusplus.github.io/LWG/lwg-active.html#2415) between shared and unique pointer when it gets to deleting null pointers. – Kerrek SB Oct 14 '14 at 12:35
  • 1
    Yes, you're missing something trivial: you apparently haven't looked at how `unique_ptr` is declared or read any documentation about it – Jonathan Wakely Oct 14 '14 at 12:50
  • I wonder what the error message would have looked like if concepts passed. As it stands now, the error message was several pages long. – sp2danny Oct 14 '14 at 12:55

3 Answers3

26

Should be

unique_ptr<FILE, int(*)(FILE*)>(fopen("file.txt", "rt"), &fclose);

since http://en.cppreference.com/w/cpp/memory/unique_ptr

or, since you use C++11, you can use decltype

std::unique_ptr<FILE, decltype(&fclose)>
ForEveR
  • 55,233
  • 2
  • 119
  • 133
12

The above answer while its intent is OK and in practice compiles and works is wrong, because it is not specified that you are allowed to take the address of a standard library function. A C++ library implementation is allowed to provide different overloads or more parameters (with default arguments). Only calling the library function is sanctioned by the standard. Therefore, you need to wrap the call to fclose in your own function implementation or lambda, such as

unique_ptr<FILE, int(*)(FILE*)>(fopen("file.txt", "rt"),
   [](FILE *fp)->int{ if(fp) return ::fclose(fp); return EOF;});

or wait for unique_resourceof https://wg21.link/p0052 to become standardized, but even there you need to use the lambda or a deleter function (object), see the more recent versions of p0052.

PeterSom
  • 2,067
  • 18
  • 16
  • 3
    You can distinguish between overload sets by explicitly casting the function type. – Konrad Rudolph Apr 09 '18 at 14:45
  • does this apply also to functions from the c library? – sp2danny Apr 09 '18 at 19:36
  • Yes! The C Library is "imported" into the C++ standard in the global namespace and in the namespace std. If I understand it correctly the aspect of not being allowed to take the address of a library function also applies to the C library functions. For example, in there are additional overloads added. – PeterSom Apr 13 '18 at 11:37
  • To be able to distinguish between function overloads when taking the address of a function does not solve the problem that you are not allowed to take the address of a standard library function. That there are overloads, does not mean that the overload that you call is actually the overload you can access through casting, it might have default arguments, which do not provide additional overloads to the set. – PeterSom Apr 17 '18 at 16:31
  • 1
    I would consider `&fclose` not working a defect rather than a bug. – sp2danny Jul 23 '18 at 18:48
  • A standard library implementation is allowed to define fclose() as a macro or to have overloads. So taking the address of a function defined in the standard library is not allowed, unless explicitly specified. That is not a defect. – PeterSom Aug 06 '18 at 14:42
  • The lambda expects a `int` return value, just `return ::fclose(fp):` – aggsol Feb 19 '20 at 09:49
  • It **is** allowed. fclose (in stdio.h) is a **C function**, std::fclose in (cstdio) is the C++ one. C functions cannot have overloads, and even if the function is defined as a function macro, &fclose will return the address of the original, because it has no parentheses (just like &getc and &putc is legal in C, even though they can be macros). – Evan Dark Apr 14 '20 at 10:40
  • Unfortunately Evan Dark, that is not actually true regarding to the C++ standard. While C++ provides all the stdio.h functions in the global namespace, the standard does not guarantee that you can take their addresses. The reason for that is to give implementors of the standard library leeway. On many systems, however, taking the address of fclose works, but as I said, it is not sanctioned by ISO C++ (i.e. [headers]p6. [using.linkage]p2, and because taking address is not explicitly allowed) – PeterSom Apr 14 '20 at 14:36
4

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.