Issue
I'm trying to use gnuplot from C++ application, gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04). I've encountered strange behavior concerning plotting to files.
So, the reproducible example is:
#include <iostream>
#include <filesystem>
int main()
{
// whatever valid filename
std::string name1 = "/tmp/1.png";
// open gnuplot pipe
auto gp = popen("gnuplot", "w");
// plot sin(x) to file. Note "unset output" in the end.
std::string cmd="set term png\nset output '"+name1+"'\nplot sin(x)\nunset output\n";
// send the command to gnuplot
fwrite(cmd.c_str(), sizeof(char), cmd.length(), gp);
std::error_code ec;
// removing the file
if (!std::filesystem::remove(name1, ec))
std::cout<<"unsuccesfully: "<<ec.value()<<"\s"<<ec.message()<<"\n";
pclose(gp);
return 0;
}
The output is (very strange):
unsuccesfully: 0 Success
What happens: gnuplot successfully writes a valid png file to desired destination. However, std::filesystem::remove does not remove the file, returns false
and (therefore) prints cryptic message about success with error code 0.
Moving pclose(gp);
line before std::filesystem::remove
solves the problem, so it does look like gnuplot holds the file. What is also strange that if I do the same manually, I mean, I launch gnuplot, issue the same command, and not exit, I'm able to remove the file with unlink /tmp/1.png
. I'm aware about gnuplot's set output
or unset output
requirement, and tried both variants.
Why the std::filesystem::remove
acts this strange?
Solution
Why the std::filesystem::remove acts this strange?
You seem to misunderstand the return value and the error value (ec
in your code) of std::filesyste::remove()
. The function does not raise an error even if there is no file you are trying to remove. The function returns false
if there is no file you are trying to remove; otherwise, it returns true
. See the document of 'std::filesystem::remove()' in the draft of C++17.
Effects: If exists(symlink_status(p, ec)), the file p is removed as if by POSIX remove().
Returns: false if p did not exist, otherwise true. The signature with argument ec returns false if an error occurs.
Since no error is raised just because there are no files to be removed, ec.value()
in your code will return 0
, indicating successful completion.
It's a bit like the behavior of the UNIX command 'rm -f'.
You can check the behavior of std::filesyste::remove()
with inserting the following into your code.
std::error_code ec;
int retval = std::filesystem::remove(name1, ec);
if ( ! ec ) { // Success
std::cout<<"succesfully: \n";
if ( retval ) {
std::cout<<"file existed and removed\n";
}
else {
std::cout<<"file didn't exist\n";
}
}
else { // No error
std::cout<<"unsuccesfully: "<<ec.value()<<" "<<ec.message()<<"\n";
}
Addition
The reason why the position of pclose()
changes the result is that the stream opened by popen()
is buffered.
When std::filesystem::remove()
is called, the command written by fwrite()
is not yet received by gnuplot because of the buffering. Therefore, in this step, the file "/tmp/1.png" has not been created yet.
Then, when pclose()
is called, gnuplot receives the commands, and the file "/tmp/1.png" is created by gnuplot. The file that you looked at was the one created after calling std::filesystem::remove()
.
You can flush the buffer explicitly using the function fflush()
. However, even if you use fflush()
, there is still a possibility that std::filesystem::remove()
will be called before the gnuplot command has finished due to the asynchronous nature of popen.
To ensure that the file is erased after gnuplot process finished, you will need the implementation (or wrapper libraries) that gnuplot and c++ program can be synchronized.
Answered By - binzo Answer Checked By - David Marino (WPSolving Volunteer)