Przekazywanie pustego wskaźnika do nowego miejsca
Domyślne miejsce docelowenew
operator jest zadeklarowany w 18.6 [support.dynamic] ¶ 1 ze specyfikacją wyjątków bez rzucania:
void* operator new (std::size_t size, void* ptr) noexcept;
Ta funkcja nie robi nic opróczreturn ptr;
więc rozsądne jest, aby tak byłonoexcept
jednak zgodnie z 5.3.4 [expr.new] ¶ 15 oznacza to, że kompilator musi sprawdzić, czy nie zwraca wartości NULL przed wywołaniem konstruktora obiektu:
-15-
[Uwaga: chyba że funkcja alokacji zostanie zadeklarowana ze specyfikacją wyjątku niepozwalającą na rzucanie (15.4), wskazuje to na nieprzydzielenie pamięci przez rzuceniestd::bad_alloc
wyjątek (Klauzula 15, 18.6.2.1); w przeciwnym razie zwraca wskaźnik inny niż null. Jeśli funkcja alokacji zostanie zadeklarowana ze specyfikacją wyjątków niepozwalającą na rzucanie, zwraca wartość null, aby wskazać, że nie przydzielono pamięci, a wskaźnik inny niż null.—End Uwaga] Jeśli funkcja alokacji zwraca wartość null, inicjalizacja nie zostanie wykonana, funkcja zwalniania nie będzie wywoływana, a wartość nowego wyrażenia będzie null.
Wydaje mi się, że (specjalnie do umieszczenianew
, nie ogólnie), ten test zerowy jest niefortunnym trafieniem wydajności, choć niewielkim.
Debugowałem jakiś kod w miejscu umieszczenianew
był używany w bardzo wrażliwej na wydajność ścieżce kodu w celu ulepszenia generowania kodu kompilatora i sprawdzenia wartości null w zespole. Zapewniając miejsce docelowe dla danej klasynew
przeciążenie zadeklarowane przez specyfikację rzucania wyjątku (nawet jeśli nie można go wyrzucić) gałąź warunkowa została usunięta, co pozwoliło kompilatorowi wygenerować mniejszy kod dla otaczających funkcji wbudowanych. Wynik powiedzenia miejscanew
funkcjonowaćmógłby rzucać, nawet jeśli tonie mógł, był wymiernie lepszy kod.
Zastanawiałem się więc, czy sprawdzanie zerowe jest naprawdę wymagane do umieszczenianew
walizka. Jedynym sposobem na zwrócenie wartości null jest przekazanie jej wartości null. Chociaż możliwe i pozornie legalne jest pisanie:
void* ptr = nullptr;
Obj* obj = new (ptr) Obj();
assert( obj == nullptr );
Nie rozumiem, dlaczego byłoby to przydatne, sugeruję, że byłoby lepiej, gdyby programista musiał jawnie sprawdzić wartość null przed użyciem umieszczenianew
na przykład
Obj* obj = ptr ? new (ptr) Obj() : nullptr;
Czy ktoś kiedykolwiek potrzebował miejscanew
poprawnie obsłużyć przypadek zerowego wskaźnika? (tj. bez wyraźnego sprawdzenia tegoptr
jest prawidłową lokalizacją pamięci.)
Zastanawiam się, czy rozsądnie byłoby zabronić przekazywania pustego wskaźnika do domyślnego położenianew
funkcja, a jeśli nie, to czy istnieje lepszy sposób na uniknięcie niepotrzebnej gałęzi, poza próbą poinformowania kompilatora, że wartość nie jest null, np.
void* ptr = getAddress();
(void) *(Obj*)ptr; // inform the optimiser that dereferencing pointer is valid
Obj* obj = new (ptr) Obj();
Lub:
void* ptr = getAddress();
if (!ptr)
__builtin_unreachable(); // same, but not portable
Obj* obj = new (ptr) Obj();
N.B. To pytanie jest celowo oznaczane jako mikro-optymalizacjanie sugerując, że obejdziesz przeładowanie miejscanew
dla wszystkich typów, aby „poprawić” wydajność. Efekt ten został zauważony w bardzo specyficznym przypadku krytycznym i oparty na profilowaniu i pomiarze.
Aktualizacja: DR 1748 czyni to niezdefiniowanym zachowaniem użycie wskaźnika zerowego z nowym położeniem, więc kompilatory nie są już wymagane do sprawdzania.