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.