2013-04-02 20 views
5

In der Theorie sollte ich in der Lage sein, einen benutzerdefinierten Zeigertyp und Deleter zu verwenden, damit unique_ptr ein Objekt verwaltet, das kein Zeiger ist. Ich habe versucht, den folgenden Code:Verwenden von unique_ptr zum Steuern eines Dateideskriptors

#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 

Diese nicht (gcc 4.7 mit dem -std = C++ 11 Parameter) funktioniert. Es reagiert mit den folgenden Fehler:

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!=' 

in die Definition von unique_ptr Von einzutauchen, kann ich zwei Probleme sehen, dass es von der Arbeit verhindern. Der erste, der eindeutig gegen den Standard verstößt, ist, dass der Destruktor für unique_ptr den "Zeiger" (der gemäß meiner Definition ein int ist) mit nullptr vergleicht, um zu sehen, ob er initialisiert ist oder nicht. Dies steht im Gegensatz zu der Art und Weise, wie es durch die boolesche Umwandlung berichtet wird, die es mit "pointer()" (einem nicht initialisierten "Zeiger") vergleicht. Dies ist die Ursache für die Fehler, die ich sehe - eine Ganzzahl ist nicht mit einem Nullptr vergleichbar.

Das zweite Problem ist, dass ich eine Möglichkeit brauche, unique_ptr zu sagen, was ein nicht initialisierter Wert ist. Ich möchte das folgende Snippet arbeiten:

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

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

Damit das funktioniert, unique_ptr wissen muss, dass ein „nicht initialisierte Wert“ -1 ist, als Null gültiger Dateideskriptor ist.

Ist das ein Fehler in gcc, oder versuche ich hier etwas zu tun, was einfach nicht möglich ist?

Danke, Shachar

+0

Ich werde die offensichtliche hinzufügen. Anstelle von "int" kann ich den Zeigertyp auf eine Klasse setzen, die ich erfinde. So kann ich alles machen, was ich möchte. Dies wird jedoch keine triviale Klasse sein, da es implizite Umwandlungen in int und andere Dinge erfordert, die ich lieber vermeiden würde. –

+3

Ich schlage vor, Sie aufhören zu verwenden 'std :: unique_ptr' für Non-Pointer-Speicher. Es erwartet, dass die Daten tatsächlich _be_ ein Zeiger sind, während Sie möchten, dass es ein _non-Zeiger_ ist. –

+0

Umschließen Sie die Datei-APIs in einer anderen Klasse (RAII), die die Datei öffnet und den Dateideskriptor speichert. Die Klasse sollte den Deskriptor schließen, wenn der Destruktor aufgerufen wird. Verwenden Sie dann den eindeutigen Zeiger einer solchen Klasse. –

Antwort

6

Der von den Deleter::pointer ausgesetzt Typ the NullablePointer requirements erfüllen muss. Unter ihnen muss dieser Ausdruck legal sein: Deleter::pointer p = nullptr;. Natürlich ist nullptr ziemlich durch die Tatsache definiert, dass es nicht implizit in eine int konvertiert werden kann, funktioniert das nicht.

Sie müssen einen Typ verwenden, der implizit mit std::nullptr_t erstellt werden kann. Etwas wie folgt aus:

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; 
}; 

Sie können, dass als Deleter::pointer Typ verwenden.

+0

Haben Sie eine Referenz auf die NullablePointer-Anforderung für unique_ptr? –

+0

@ShacharShemesh: Ja. Es ist genau dort in der Antwort verlinkt. –

+4

Sie sollten '-1' für' nullptr' verwenden. '0' ist ein gültiger fd (obwohl selten geschlossen). –

5

Können Sie etwas Einfaches wie das Folgende tun?

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; 
}; 

Ich sehe nicht, warum Sie unique_ptr verwenden musste, die Zeiger verwalten ausgelegt ist.

0

Diese Lösung wird auf Nicol Bolas Antwort basiert:

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

es kurz, aber Sie haben zu vermeiden UniqueFd Instanz mit nullptr und verwenden Sie es als Booleschen Ausdruck zu vergleichen:

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

Herstellung Nicol Bolas Klasse allgemeiner:

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 
// ... 

Ich bin mir nicht sicher, ob Ich sollte Standardvorlagenparameterwerte haben oder nicht.

0

Ein komplettes Beispiel:

#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); 
} 
Verwandte Themen