2016-11-07 11 views
6

Pracuję nad pewną przestrzenią pamięci z niestandardowym przydziałem i usuwaniem, które odbywa się przy użyciu interfejsu podobnego do malloc, którego nie kontroluję (tj. Przydzielam n bajtów lub zwolnię przydzielony ptr). Nic takiego jak delete [].Jak używać miejsca docelowego nowego z niestandardowym interfejsem API?

Teraz chcę zbudować tablicę T-ów. Dostaję miejsce na to z auto space_ptr = magic_malloc(n*sizeof(T)). Teraz chcę zrobić coś takiego jak rozmieszczenie tablic-nowości, aby utworzyć n elementów w miejscu. Jak mam to zrobić? ... czy powinienem po prostu zapętlić od 1 do n i skonstruować pojedyncze T?

Uwaga:

  • jestem ignorując kwestie wyrównania tutaj (albo raczej, zakładając alignof(T) dzieli sizeof(T)). Jeśli chcesz zaadresować wyrównanie, byłoby to jeszcze lepsze, ale możesz je zignorować dla uproszczenia.
  • Kod powitalny C++ 11 (preferowany, w rzeczywistości), ale bez C++ 14/17.
+0

Istnieje nowa wersja miejsca docelowego dla tablic ... ale mogą występować problemy z wyrównaniem. Czy [to] (http://coliru.stacked-crooked.com/a/01699890c4ae1ac0) to, czego potrzebujesz? – AndyG

+0

@AndyG: Prawdopodobnie nie, ponieważ umieszczenie 'new []' używa spacji do zapisania informacji, które będą używane przez 'delete []'. – einpoklum

Odpowiedz

5

Założę się, że twoja pamięć jest wystarczająco wyrównana dla twojego T. Prawdopodobnie chcesz to sprawdzić.

Następnym problemem są wyjątki. Naprawdę powinniśmy napisać dwie wersje, jedną z możliwością, że konstruowanie spowoduje wyjątek, a jeden bez.

Napiszę bezpieczną wersję wyjątku.

template<class T, class...Args> 
T* construct_n_exception_safe(std::size_t n, void* here, Args&&...args) { 
    auto ptr = [here](std::size_t i)->void*{ 
    return static_cast<T*>(here)+i; 
    }; 
    for(std::size_t i = 0; i < n; ++i) { 
    try { 
     new(ptr(i)) T(args...); 
    } catch(...) { 
     try { 
     for (auto j = i; j > 0; --j) { 
      ptr(j-1)->~T(); 
     } 
     } catch (...) { 
     exit(-1); 
     } 
     throw; 
    } 
    } 
    return static_cast<T*>(here); 
} 

a nie wyjątek bezpieczna wersja:

template<class T, class...Args> 
T* construct_n_not_exception_safe(std::size_t n, void* here, Args&&...args) { 
    auto ptr = [here](std::size_t i)->void*{ 
    return static_cast<T*>(here)+i; 
    }; 
    for(std::size_t i = 0; i < n; ++i) { 
    new (ptr(i)) T(args...); 
    } 
    return static_cast<T*>(here); 
} 

Można zrobić system oparty tag-wysyłka wybierać między nimi w zależności czy konstruowania T z Args&... rzuca czy nie. Jeśli rzuca, a ->~T() nie jest trywialne, użyj wyjątku bezpiecznego.

C++ 17 udostępnia kilka nowych funkcji do wykonania dokładnie tych zadań. Prawdopodobnie zajmują się narożnymi kopalniami, a nie kopią.


Jeśli próbujesz naśladować new[] i delete[], jeśli T ma nietrywialne dtor będziesz musiał umieścić ile T utworzony w bloku.

Typowym sposobem, aby to zrobić, jest poprosić o dodatkowy pokój dla licznika przy z przodu bloku. Tj., Pytaj o sizeof(T)*N+K, gdzie K może być sizeof(std::size_t).

Teraz w emulatorze new[], rzeczy N do pierwszego bitu, a następnie zadzwoń construct_n na bloku zaraz po nim.

W delete[] odejmij sizeof(std::size_t) od wskazanego kursora, przeczytaj N, a następnie zniszcz obiekty (od prawej do lewej, aby odzwierciedlić kolejność konstrukcji).

Wszystko to będzie wymagało zachowania ostrożności try - catch.

Jeśli jednak ~T() jest trywialne, zarówno swoje emulowane new[] i delete[] nie przechowywać ten dodatkowy std::size_t ani nie czytać.

(Zauważ, że jest to jak naśladowaćnew[] i delete[]. Jak dokładnie new[] i delete[] praca jest zależna od implementacji. Ja tylko szkicowanie na jeden sposób można je naśladować, to może nie być zgodne z tym, jak one działają w systemie. na przykład, niektóre Abis może zawsze przechowywać N nawet jeśli ->~T() jest trywialna lub mnóstwo innych wariantów.)


Jak zauważył OP, można również sprawdzić za trywialny budowy przed kłopotać się powyższym.

+0

Czy jesteśmy pewni, że zostanie to zoptymalizowane pod kątem T za pomocą trywialnego konstruktora? Po cóż, dlaczego wyjść (-1) z rzutu niszczenia? – einpoklum

+0

@einpoklum Ponieważ już przekazujemy rzut o tej porze. Budowa rzuciła. Niszczyciel rzucił. Jesteśmy w zasadzie skręceni. Nie jestem pewien, czy jest zoptymalizowany; Sprawdziłbym również to, ale najpierw wdrożyłem powyższe i potwierdziłem, że powoduje niepotrzebne zapętlenie. Przynajmniej wykrycie i pominięcie tego trywialnego fragmentu sprawi, że debugowanie będzie mniej absurdalne. :) Używałbyś wysyłania tagów i 'std :: is_trivial' lub somesuch. – Yakk

+1

Należy zachować ostrożność podczas wstawiania "size_t" na początku alokacji. Sama alokacja jest zwykle wyrównana, ale dodatkowe 'size_t' może powodować niewspółosiowość. Wpadłem na ten problem w aplikacjach świata rzeczywistego, gdzie klang użył SIMD do zainicjowania dwóch debelów w klasie, wymagając pełnego 'alignof (max_align_t)' gwarantowanego przez StdLib 'new' /' malloc', podczas gdy 'sizeof (size_t) dyp

Powiązane problemy