Jacob
Jacob

Reputation: 65

Trying to std::move on std::unique_ptr holding FILE ptr and passing it to struct constructor

I'm following the book "C++ Crash Course" by J.Lospinoso, and I'm currently on Chapter 11 presenting smart pointers.

In the first exercise, the reader has to edit the below code with file handling when using std::unique_ptr:

#include <iostream>
#include <memory>

using FileGuard = std::unique_ptr<FILE, int(*)(FILE*)>;

void say_hello(FileGuard file){
    fprintf(file.get(), "HELLO DAVE");
}

int main() {

    auto file = fopen("HAL9000", "w");
    if(!file) return errno;
    FileGuard file_guard{ file, fclose };
    //File open here
    say_hello(std::move (file_guard));
    //File closed here
    return 0;
}

The case is to change std::unique_ptr to std::shared_ptr.

After this, we can pass file_guard to the say_hello() function without std::move(), and make more calls to say_hello(), everything works fine.

In the next exercise, the reader has to write a class like below, it's already a little bit modified:

#include <iostream>
#include <memory>

using FileGuard = std::shared_ptr<FILE>;

struct Hal {
    Hal(std::shared_ptr<FILE> file):file(file){}
    ~Hal(){
        fprintf(file.get(), "Stop, Dave\n");
    }
    void write_status(const char* ch, int len){
        fprintf(file.get(), ch);
    }
    std::shared_ptr<FILE> file;
};

int main() {
    
    auto file = fopen("HAL9000", "w");
    if(!file) return errno;
    //{
    FileGuard file_guard{ file, fclose};
    Hal hal1{std::move(file_guard)};
    hal1.write_status("I'm completely operational1\n", 8);
    if (file) std::cout<<"File is not closed\n";
    //} 
    fprintf(file, "I can still write after std::move..\n"); 
    return 0;
}

My question is, why can I still write to the file line "I can still write after std::move..\n" if I have moved file_guard to the Hal object? Is it overall correct syntax?

When uncommenting the braces in main() to create scope, I don't have access to the file anymore, because as I hope fclose() is invoked when hal1 object is destructed, isn't it?

I know there is lot of C, but this is how the book is written, at least to some place.

Compiling on Windows 11 with MSVC 19.33.31629.


EDIT:

int main() {
    
    auto file = fopen("HAL9000", "w");
    if(!file) return errno;
    FileGuard file_guard{std::move(file), fclose};
    {
    Hal hal1{file_guard}; //adding std::move causing undef behaviour? Nothing saved to file anyway.
    hal1.write_status("I'm completely operational1\n", 8);
    }
    Hal hal2{file_guard};
    hal2.write_status("I'm completely operational2\n", 8);
    fprintf(file, "I can still write after std::move..\n"); 
    return 0;
}

Upvotes: 0

Views: 171

Answers (1)

mCoding
mCoding

Reputation: 4859

In the first example, you move into the argument of say_hello, but it is the } at the end of say_hello that triggers the destructor of the file guard to run and close the file, not the move.

Likewise, the line

Hal hal1{std::move(file_guard)};

does not cause the destructor of file_guard to run nor does it close the file any other way. The file is kept open at long as there is a shared_ptr alive that points to file. Move constructing one shared pointer from another does not change the reference count of the pointed-to object and therefore will never run the destructor of the pointed-to object. It is only when the destructor of hal1 runs at the end of main that the shared_ptr it owns gets destructed and closes the file, so the file is still open at the time of your fprintf.

If you uncomment those curly braces, the destructor of hal1 will run at the closing }. Calling fprintf on a closed file is undefined behavior and as such the program is undefined and what actually happens depends on your compiler.

Upvotes: 2

Related Questions