Default-Initialisierung eines void* mit -1
-
Es ist bei C++17 wohl nur noch
nullptr
als direkte Adresszuweisung (als Template-Parameter) erlaubt.Der MSVC nimmt es aber weiterhin (nur CLANG und GCC nicht): Godbolt Code.
-
Eien Pointer kannst du dir mit memcpy erzeugen:
void* make_invalid_ptr() { void *ptr; uintptr_t i = -1; std::memcpy(&ptr, &i, sizeof ptr); return ptr; }
(ungetestet)
-
Aber nicht als Template-Parameter!
-
@DocShoe sagte in Default-Initialisierung eines void* mit -1:
Trotzdem ist das Ganze iwie merkwürdig. Warum geht das mit C++17 plötzlich nicht mehr?
Der Standard sagt offenbar schon länger, dass u.a. das Resultat von
reinterpret_cast
keine Constant Expression ist. Scheinbar waren die Compiler vorher etwas toleranter in der Auslegung.Ich habe jetzt nach einiger Sucherei auch keine Ein-Zeilen-Lösung gefunden und würde das selbst wahrscheinlich so lösen:
#include <memory> #include <cstdint> using HANDLE = void*; template <typename ObjectType> struct SharedObjectTraits; template <> struct SharedObjectTraits<HANDLE> { static HANDLE invalid_value; }; HANDLE SharedObjectTraits<HANDLE>::invalid_value = reinterpret_cast<HANDLE>(std::intptr_t{ -1 }); template <typename ObjectType> class SharedObject { struct Holder { ObjectType Object = SharedObjectTraits<ObjectType>::invalid_value; }; std::shared_ptr<Holder> Holder_; public: SharedObject() = default; }; using SharedWin32Handle = SharedObject<HANDLE>; int main() { SharedWin32Handle test; return 0; }
Die Traits-Klasse hilft, die Anzahl der notwenigen Template-Parameter zu reduzieren (finde ich beser zu lesen, da es weniger Code-Rauschen erzeugt). Man kann aber auch einen "Invalid Value"-Typen mit so einem Static Member als Template-Parameter übergeben, wenn dir das sinnvoller erscheint.
Der Ansatz ist insofern auch besser, als dass er generell nicht auf Constant Expressions angewiesen ist, was die ungültigen Werte angeht. Vielleicht hat man es ja auch mal mit einem
ObjectType
zu tun, wo völlig klar ist, dass man den Wert nicht durch einenconstexpr
-Ausdruck schieben kann. Z.B. wenninvalid_value
aus einer Bibliothek kommt und man nur viaextern
-Variable darauf zugreifen kann.Edit:
invalid_value
muss natürlich einstatic
Member sein.
-
Danke Finnegan, aber mit type traits funktioniert das leider nicht. Die WINAPI ist da leider inkonsistent, manche Funktionen, die ein
HANDLE
als Rückgabewert haben, liefern 0 (nullptr) zurück, andere -1 (INVALID_HANDLE_VALUE). Den ungültigen Wert per type trait zu bestimmen geht nicht, weil ich anhand des HANDLE nicht bestimmen kann, welcher der beiden Werte jetzt gebraucht wird.
-
@DocShoe sagte in Default-Initialisierung eines void* mit -1:
Danke Finnegan, aber mit type traits funktioniert das leider nicht. Die WINAPI ist da leider inkonsistent, manche Funktionen, die ein
HANDLE
als Rückgabewert haben, liefern 0 (nullptr) zurück, andere -1 (INVALID_HANDLE_VALUE). Den ungültigen Wert per type trait zu bestimmen geht nicht, weil ich anhand des HANDLE nicht bestimmen kann, welcher der beiden Werte jetzt gebraucht wird.Dann übergebe den "Invalid Value"-Typen einfach per Template-Parameter:
template <typename ObjectType, typename InvalidValueType> class SharedObject { ... ObjectType Object = InvalidValueType::invalid_value; }; ... using SharedWin32Handle = SharedObject<HANDLE, SharedObjectTraits<HANDLE>>; // oder welchen Typen auch immer: using SharedWin32Handle2 = SharedObject<HANDLE, InvalidValueType2>; using SharedWin32Handle3 = SharedObject<HANDLE, InvalidValueType3>;
Praktisch ist auch, dass man in der Klasse, die man als zusätzlichen Template-Parameter übergibt (oder als Traits-Klasse definiert), z.B. auch den Code unterbringen kann, um das Handle wieder freizugeben. Ich vermute mal da gibt es in Win32 auch vercheidene Funtionen für, je nachem, was für eine Art Handle es ist (?).
-
Und da beißt sich die Katze wieder in den Schwanz:
Code auf C++ShellHat den Fehler jetzt aus der Holder-Klasse in die Type-Traits Klasse verschoben
-
@DocShoe sagte in Default-Initialisierung eines void* mit -1:
Und da beißt sich die Katze wieder in den Schwanz:
Code auf C++ShellHat den Fehler jetzt aus der Holder-Klasse in die Type-Traits Klasse verschoben
Ich hatte in meiner Traits-Lösung das hier verlinkt (unter "so lösen").
Du kannst nicht implizit von
int
nachvoid*
konvertieren. Du muss das schon explizit machen.Und btw.: Es sind zwei Fehler hier. Einmal die implizite Konvertierung und dann (was mich auch überrascht hat) dass
reinterpret_cast
(die naheliegende Lösung für die Konvertierung) nicht imconstexpr
-Kontext erlaubt ist.
-
Jau, geht
Jetzt muss ich mir mal Zeit nehmen und gucken, ob sich das so umsetzen lässt.
-
@Finnegan sagte in Default-Initialisierung eines void* mit -1:
Praktisch ist auch, dass man in der Klasse, die man als zusätzlichen Template-Parameter übergibt (oder als Traits-Klasse definiert), z.B. auch den Code unterbringen kann, um das Handle wieder freizugeben. Ich vermute mal da gibt es in Win32 auch vercheidene Funtionen für, je nachem, was für eine Art Handle es ist (?).
Ja, für sowas habe ich Allocator-Klassen, die als Template Parameter übergeben werden. Tun hier aber nichts zur Sache, also habe ich sie ausgelassen.
-
@DocShoe sagte in Default-Initialisierung eines void* mit -1:
@Finnegan sagte in Default-Initialisierung eines void* mit -1:
Praktisch ist auch, dass man in der Klasse, die man als zusätzlichen Template-Parameter übergibt (oder als Traits-Klasse definiert), z.B. auch den Code unterbringen kann, um das Handle wieder freizugeben. Ich vermute mal da gibt es in Win32 auch vercheidene Funtionen für, je nachem, was für eine Art Handle es ist (?).
Ja, für sowas habe ich Allocator-Klassen, die als Template Parameter übergeben werden. Tun hier aber nichts zur Sache, also habe ich sie ausgelassen.
Macht es vielleicht Sinn,
invalid_value
zu einemstatic
Member dieser Klassen zu machen? Nur so als Anregung - das könnte den Code etwas kompakter machen und einiges an Boilerplate reduzieren. Welcher Handle-Wert ungültig ist, hängt doch sicher an den Funktionen, mit denen man das Objekt, auf welches das Handle verweist, erstellt, a.k.a "Allokations"-Funktionen, wenn ich das richtig verstehe (?). Das klingt für mich erstmal so, als obinvalid_value
vom Design her gut in diese Allocator-Klassen passen würde.
-
Geht sogar noch einfacher
Ich habe ja vorhin die Allocator-Klasse erwähnt, die für die Freigabe des gehaltenen Objekts verantwortlich ist. Die bekommt jetzt einfach noch ne statische Methode, die den ungültigen Objekttyp zurückgibt.
Im ersten Ansatz sähe das jetzt so aus:#include <memory> struct HandleAllocator { static void release( HANDLE object ) { CloseHandle( object ); } static HANDLE invalid_object_value() { return INVALID_HANDLE_VALUE; } }; template<typename ObjectType, typename Allocator> class SharedObject { struct Holder { ObjectType Object = Allocator::invalid_object_value(); Holder() = default; Holder( ObjectType object ) { Object = object; } ~Holder() { Allocator::release( Object ); } }; std::shared_ptr<Holder> Holder_; ... }; using Win32SharedHandle = SharedObject<HANDLE, HandleAllocator>; int main() { Win32SharedHandle sh; }
-
@DocShoe sagte in Default-Initialisierung eines void* mit -1:
Geht sogar noch einfacher
Ich habe ja vorhin die Allocator-Klasse erwähnt, die für die Freigabe des gehaltenen Objekts verantwortlich ist. Die bekommt jetzt einfach noch ne statische Methode, die den ungültigen Objekttyp zurückgibt.Finde ich sogar noch besser als Funktion. Flexibler und simplere Syntax.
P.S.: Wenn du die Funktion jetzt übrigens noch
constexpr
machst, dann schliesst sich der Kreis wieder und wir können die Diskussion von vorne beginnen... SCNR
-
@Finnegan sagte in Default-Initialisierung eines void* mit -1:
P.S.: Wenn du die Funktion jetzt übrigens noch
constexpr
machst, dann schliesst sich der Kreis wieder und wir können die Diskussion von vorne beginnen... SCNRIch hab´ Zeit