I created a generic deleter template that can be used to create unique_ptr<>() sub-types allowing for a Deleter other than just delete ptr.
It works great with the default optimization flags (i.e. -O0), however, when I use -O3 the T & operator * () function, somehow, returns 0 instead of the f_pointer contents.
I would like to make sure that we agree that there is something wrong in the compiler and that my template is correct. The following is a complete piece of code that should compile as is under Ubuntu 16.04 and Ubuntu 18.04 and probably other versions as long as they support C++14 (see below for tested g++ versions).
// RAII Generic Deleter -- allow for any type of RAII deleter
//
// To break compile with:
// g++ --std=c++14 -O3 -DNDEBUG ~/tmp/b.cpp -o b
#include <memory>
#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
template<class T, T null_value, class D, D deleter>
class raii_generic_deleter
{
public:
class pointer
{
private:
T f_pointer = null_value;
public:
pointer(T p)
: f_pointer(p)
{
}
pointer(std::nullptr_t = nullptr)
: f_pointer(null_value)
{
}
explicit operator bool () const
{
return f_pointer != null_value;
}
bool operator == (pointer const rhs) const
{
return f_pointer == rhs.f_pointer;
}
bool operator != (pointer const rhs) const
{
return f_pointer != rhs.f_pointer;
}
T & operator * ()
{
return f_pointer;
}
};
void operator () (pointer p)
{
deleter(*p);
}
};
typedef std::unique_ptr<int,
raii_generic_deleter<int, -1, decltype(&::close), &::close>>
raii_fd_t;
int main(int argc, char * argv [])
{
int fd = -1;
{
raii_fd_t safe_fd;
std::cout << "default initialization: safe_fd = " << *safe_fd
<< std::endl;
fd = open("/tmp/abc.tmp", O_RDWR | O_CREAT, 0700);
std::cout << "fd = " << fd << std::endl;
safe_fd.reset(fd);
std::cout << "safe_fd after the reset(" << fd
<< ") = " << *safe_fd << std::endl;
}
if(fd != -1)
{
// assuming the safe_fd worked as expected, this call returns an error
//
int r = close(fd);
int e(errno);
std::cout << "second close returned " << r
<< " (errno = " << e << ")" << std::endl;
}
return 0;
}
(For original, see raii_generic_deleter.h in libsnapwebsites)
There is the output I'm getting when I use -O0 (no optimizations):
default initialization: safe_fd = -1
fd = 3
safe_fd after the reset(3) = 3
second close returned -1 (errno = 9)
In this case the *safe_fd call returns -1 and 3 as expected. This calls the template T & pointer::operator * () function.
With any level of optimization (-O1, -O2, -O3) the output looks like this:
default initialization: safe_fd = 0
fd = 3
safe_fd after the reset(3) = 0
second close returned -1 (errno = 9)
As we can see, the safe file descriptor returns 0 instead of -1 after initialization and then again 0 when it should then be 3. However, the destructor properly closes the file since the second close fails as expected. In other words, somehow, the file description (3) is known and properly used by the deleter.
When I update the pointer operator in this way:
T & operator * ()
{
std::cout << "f_pointer within operator * = " << f_pointer
<< std::endl;
return f_pointer;
}
Then the output with any level of optimization is correct:
f_pointer within operator * = -1
default initialization: safe_fd = -1
fd = 3
f_pointer within operator * = 3
safe_fd after the reset(3) = 3
f_pointer within operator * = 3
second close returned -1 (errno = 9)
Which is probably because that specific function doesn't get optimized out completely.
Compilers:
I tested with stock g++ on Ubuntu 16.04
g++ (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609
And also on Ubuntu 18.04
g++ (Ubuntu 7.3.0-16ubuntu3) 7.3.0
I also went ahead and reported this as a bug on the GNU website.