Я создал универсальный шаблон удаления, который можно использовать для создания unique_ptr<>()
подтипов, допускающих Deleter
, отличных от delete ptr
.
Отлично работает с флагами оптимизации по умолчанию (т.е. -O0
), однако, когда я использую -O3
, функция T & operator * ()
почему-то возвращает 0
вместо содержимого f_pointer
.
Я хотел бы убедиться, что мы согласны с тем, что в компиляторе что-то не так и мой шаблон верен. Ниже приведен полный фрагмент кода, который должен быть скомпилирован в соответствии с Ubuntu 16.04 и Ubuntu 18.04 и, возможно, другими версиями, если они поддерживают C ++ 14 (см. Ниже для протестированных версий g ++).
// 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;
}
(Оригинал см. raii_generic_deleter.h на веб-сайтах libsnapwebs)
Есть вывод, который я получаю, когда использую -O0
(без оптимизации):
default initialization: safe_fd = -1
fd = 3
safe_fd after the reset(3) = 3
second close returned -1 (errno = 9)
В этом случае вызов *safe_fd
возвращает -1
и 3
, как и ожидалось. Это вызывает функцию шаблона T & pointer::operator * ()
.
При любом уровне оптимизации (-O1
, -O2
, -O3
) вывод выглядит следующим образом:
default initialization: safe_fd = 0
fd = 3
safe_fd after the reset(3) = 0
second close returned -1 (errno = 9)
Как мы видим, дескриптор безопасного файла возвращает 0
вместо -1
после инициализации, а затем снова 0
, когда оно должно быть 3
. Однако деструктор правильно закрывает файл, так как второе закрытие завершается неудачно, как и ожидалось. Другими словами, каким-то образом описание файла (3
) известно и правильно используется удалителем.
Когда я обновляю оператор указателя следующим образом:
T & operator * ()
{
std::cout << "f_pointer within operator * = " << f_pointer
<< std::endl;
return f_pointer;
}
Тогда вывод с любым уровнем оптимизации будет правильным:
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)
Вероятно, это связано с тем, что эта конкретная функция не оптимизирована полностью.
Составители:
Я тестировал со стоковым g ++ на Ubuntu 16.04
g ++ (Ubuntu 5.4.0-6ubuntu1 ~ 16.04.9) 5.4.0 20160609
А также на Ubuntu 18.04
g ++ (Ubuntu 7.3.0-16ubuntu3) 7.3.0
Я также сообщил об этом как об ошибке на веб-сайте GNU .