2013-04-02 25 views
5

Teoretycznie powinienem móc używać niestandardowego typu wskaźnika i deletera, aby unikalny_ptr zarządzał obiektem, który nie jest wskaźnikiem. Próbowałem następujący kod:Używanie unique_ptr do kontrolowania deskryptora pliku

#ifndef UNIQUE_FD_H 
#define UNIQUE_FD_H 

#include <memory> 
#include <unistd.h> 

struct unique_fd_deleter { 
    typedef int pointer; // Internal type is a pointer 

    void operator()(int fd) 
    { 
     close(fd); 
    } 
}; 

typedef std::unique_ptr<int, unique_fd_deleter> unique_fd; 

#endif // UNIQUE_FD_H 

to nie zadziała (GCC 4.7 z -std = C++ 11 parametrów). Reaguje z następujących błędów:

In file included from /usr/include/c++/4.7/memory:86:0, 
       from test.cc:6: 
/usr/include/c++/4.7/bits/unique_ptr.h: In instantiation of 'std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = int; _Dp = unique_fd_deleter]': 
test.cc:22:55: required from here 
/usr/include/c++/4.7/bits/unique_ptr.h:172:2: error: invalid operands of types 'int' and 'std::nullptr_t' to binary 'operator!=' 

Od zagłębiając się w definicji unique_ptr, widzę dwa problemy, które uniemożliwiają go z pracy. Pierwszym, co wydaje się jawnym naruszeniem standardu, jest to, że destruktor dla unique_ptr porównuje "wskaźnik" (który jest, według mojej definicji, int) do nullptr, aby sprawdzić, czy jest on zainicjalizowany czy nie. Jest to sprzeczne ze sposobem, w jaki raportuje to poprzez konwersję boolowską, która polega na porównaniu go do "wskaźnika()" (niezainicjowanego "wskaźnika"). To jest przyczyną błędów, które widzę - liczba całkowita nie jest porównywalna z wartością nullptr.

Drugi problem polega na tym, że potrzebuję jakiegoś sposobu, by powiedzieć unique_ptr, czym jest niezainicjowana wartość. Chcę następujący fragment kodu do pracy:

unique_fd fd(open(something...)); 

if(!fd) 
    throw errno_exception("Open failed"); 

za to do pracy, unique_ptr musi wiedzieć, że „wartość niezainicjowany” jest -1, jak ważna jest zerowy deskryptor pliku.

Czy to błąd w gcc, czy staram się coś tutaj zrobić, czego po prostu nie da się zrobić?

Dzięki Shachar

+0

dodam oczywiste. Zamiast "int" mogę ustawić typ wskaźnika na jakąś klasę, którą wymyślam. Pozwoli mi to zrobić wszystko, co chcę. Nie będzie to jednak banalna klasa, ponieważ wymaga niejawnych rzutów do int i innych rzeczy, których wolałbym uniknąć. –

+3

Proponuję zaprzestać używania 'std :: unique_ptr' dla przechowywania non-pointer. Oczekuje, że dane rzeczywiście będą wskaźnikiem, a Ty chcesz, aby był _niespokoleniowym_. –

+0

Zawiń API pliku wewnątrz innej klasy (RAII), która otwiera plik i przechowuje deskryptor pliku. Klasa powinna zamknąć deskryptor po wywołaniu destruktora. Następnie użyj unikalnego wskaźnika takiej klasy. –

Odpowiedz

6

typu wystawiony przez Deleter::pointer muszą spełniać the NullablePointer requirements. Najważniejsze z nich, to wyrażenie musi być legalne: Deleter::pointer p = nullptr;. Oczywiście, nullptr jest prawie zdefiniowany przez fakt, że nie może być niejawnie przekształcony w int, to nie działa.

Będziesz musiał użyć typu, który może być domyślnie skonstruowany z std::nullptr_t. Coś takiego:

struct file_desc 
{ 
    file_desc(int fd) : _desc(fd) {} 
    file_desc(std::nullptr_t) : _desc(-1) {} 

    operator int() {return _desc;} 

    bool operator ==(const file_desc &other) const {return _desc == other._desc;} 
    bool operator !=(const file_desc &other) const {return _desc != other._desc;} 
    bool operator ==(std::nullptr_t) const {return _desc == -1;} 
    bool operator !=(std::nullptr_t) const {return _desc != -1;} 

    int _desc; 
}; 

że można używać jako typ Deleter::pointer.

+0

Czy zdarzają się odniesienia do wymagania NullablePointer dla unique_ptr? –

+0

@ShacharShemesh: Tak. Jest połączony bezpośrednio w odpowiedzi. –

+4

Powinieneś użyć '-1' dla' nullptr'. '0' jest prawidłowym plikiem fd (chociaż rzadko jest zamknięty). –

5

Czy możesz zrobić coś prostego, jak poniżej?

class unique_fd { 
public: 
    unique_fd(int fd) : fd_(fd) {} 
    unique_fd(unique_fd&& uf) { fd_ = uf.fd_; uf.fd_ = -1; } 
    ~unique_fd() { if (fd_ != -1) close(fd_); } 

    explicit operator bool() const { return fd_ != -1; } 

private: 
    int fd_; 

    unique_fd(const unique_fd&) = delete; 
    unique_fd& operator=(const unique_fd&) = delete; 
}; 

ja nie rozumiem, dlaczego trzeba było użyć unique_ptr, który jest przeznaczony do zarządzania wskaźniki.

0

Rozwiązanie to opiera się na Nicol Bolas odpowiedź:

struct FdDeleter 
{ 
    typedef int pointer; 
    void operator()(int fd) 
    { 
     ::close(fd); 
    } 
}; 
typedef std::unique_ptr<int, FdDeleter> UniqueFd; 

To krótki, ale trzeba unikać porównać instancji UniqueFd z nullptr i używać go jako logiczną wyrażenia:

UniqueFd fd(-1, FdDeleter()); //correct 
//UniqueFd fd(nullptr, FdDeleter()); //compiler error 
if (fd.get() != -1) //correct 
{ 
    std::cout << "Ok: it is not printed" << std::endl; 
} 
if (fd) //incorrect, avoid 
{ 
    std::cout << "Problem: it is printed" << std::endl; 
} 
if (fd != nullptr) //incorrect, avoid 
{ 
    std::cout << "Problem: it is printed" << std::endl; 
} 
return 1; 
0

Making Klasa Nicola Bolasa bardziej ogólna:

template<class T=void*,T null_val=nullptr> 
class Handle 
    { 
    public: 
     Handle(T handle):m_handle(handle){} 

     Handle(std::nullptr_t):m_handle(null_val){} 

     operator T(){return m_handle;} 

     bool operator==(const Handle& other) const 
      {return other.m_handle==m_handle;} 

    private: 
     T m_handle; 
    }; 

typedef Handle<int,-1> FileDescriptor; 
typedef Handle<GLuint,0> GlResource; // according to http://stackoverflow.com/questions/7322147/what-is-the-range-of-opengl-texture-id 
// ... 

Nie jestem pewien czy Powinienem mieć domyślne wartości parametrów szablonu.

0

Kompletny próbki:

#include <memory> 
#include <unistd.h> 
#include <fcntl.h> 

template<class T, T nullvalue, class D, D d> // auto d works in new compilers. 
struct generic_delete 
{ 
    class pointer 
    { 
     T t; 
    public: 
     pointer(T t) : t(t) {} 
     pointer(std::nullptr_t = nullptr) : t(nullvalue) { } 
     explicit operator bool() { return t != nullvalue; } 
     friend bool operator ==(pointer lhs, pointer rhs) { return lhs.t == rhs.t; } 
     friend bool operator !=(pointer lhs, pointer rhs) { return lhs.t != rhs.t; } 
     T& operator*() { return t; } 
    }; 
    void operator()(pointer p) 
    { 
     d(*p); 
    } 
}; 
using unique_fd = std::unique_ptr<int, generic_delete<int, -1, decltype(&close), &close>>; 
static_assert(sizeof(unique_fd) == sizeof(int), "bloated unique_fd"); 

int main() 
{ 
    unique_fd fd1(open("fd.txt", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)); 
    write(*fd1, "hello\n", 6); 
}