2012-03-18 10 views
5

Czy ma sens inicjowanie pamięci za pomocą std::uninitialized_fill() w bibliotece, gdy alokator przekazany jako argument przez użytkownika został użyty do uzyskania samej pamięci? Pytam o to, ponieważ alokator powinien dostarczać własną metodę construct() (inną niż metoda allocate()), której implementacja może różnić się od standardowej, więc prawdopodobnie std::uninitialized_fill() nie zawsze jest odpowiednie we wszystkich przypadkach.Czy ma sens stosowanie std :: uninitialized_fill() z dowolnym przydziałem?

Moje wątpliwości są dokładniej napisane w książce C++ napisanej przez Stroustrup (załącznik E "Standard-Library Exception Safety", sekcja E. 3.1), w której autor podaje możliwą implementację template<class T, class A> vector<T,A>::vector(size_type n, const T& val, const A& a): alokator a służy do uzyskania pamięci dla wektora, a następnie std::uninitialized_fill() służy do inicjalizacji uzyskanej pamięci.

Podaje także implementację std::uninitialized_fill(), która wewnętrznie używa standardowego miejsca docelowego new do zainicjowania pamięci, ale nie ma już dowodów na to, że metoda alokatora została przekazana jako argument do konstruktora wektorowego.

+1

Co 'std :: initialize_fill() '? Masz na myśli 'std :: uninitialized_fill()'? Funkcja 'initialize_fill()' nie pojawia się nigdzie w standardzie C++, w przeciwieństwie do 'uninitialized_fill()'. –

+0

@Insilico przepraszam, to była literówka. Dziękuję Ci. Naprawiłem pytanie. – Martin

+2

Wydaje się, że jest to związane z moim pytaniem: http://stackoverflow.com/questions/9727556/is-uninitialized-copy-fillin-first-in-last-for-dest-aa-an-oversight-in-, robię myślę, że w rzeczywistości prawdopodobnie bezpiecznie jest użyć zwykłego 'uninitialized_fill' –

Odpowiedz

2

Sprawdziłem wdrożenie GCC wektora, to brzmi:

vector(size_type __n, const value_type& __value, 
     const allocator_type& __a = allocator_type()) 
    : _Base(__n, __a) 
    { _M_fill_initialize(__n, __value); } 

    ... 

    _M_fill_initialize(size_type __n, const value_type& __value) 
    { 
    this->_M_impl._M_finish = 
     std::__uninitialized_fill_n_a(this->_M_impl._M_start, __n, __value, 
            _M_get_Tp_allocator()); 
    } 

Tak, wygląda na to informacje o podzielniki nie odejdzie. Jednak kod uninitialized_fill_n_a jest dziwny, ma dwa przeciążenia (patrz niżej), jeden dla ogólnego alokatora i drugi dla std::allocator.

Połączenia ogólne, construct i specjalne dla std::allocator po prostu wywołuje std::uninitialized_fill().

Więc mój wniosek jest następujący,

1) wszystkie std::allocator „s construct s ma ten sam skutek, jak umieszczenie nowego i

2), że nie można przyjąć dla podzielników generycznych, albo przynajmniej GCC std::vector nie zakłada tego. (Według Michaela Burra można założyć to w C++ 03, niestety nie wspomina on o C++ 11).

Dlatego używajcie konstruktu (to jest std::allocator_traits<Alloc>::construct(alloc, pointer, value)).

Moja osobista opinia (po zbadaniu tego), że

i) std::uninitialized_fill jest uszkodzony, w które należy podjąć ostatnią opcjonalny argument (lub inny przeciążenie) z podzielnika,

II), jak obejście, wewnątrz szczegółów implementacji powinieneś mieć funkcję .uninitialized_fill(first, last, value, alloc), która wykonuje zadanie, w tym obsługę wyjątków (zwróć uwagę poniżej, w jaki sposób rozwijane są konstrukcje z niszczy podczas awarii). .

iii) Aktualny std::unitialized_fill jest całkiem bezużyteczny, jeśli masz informacje o podzielniki (iw zasadzie trzeba go reimplement)


teraz kod mowa powyżej:

template<typename _ForwardIterator, typename _Size, typename _Tp, 
      typename _Allocator> 
    _ForwardIterator 
    __uninitialized_fill_n_a(_ForwardIterator __first, _Size __n, 
          const _Tp& __x, _Allocator& __alloc) 
    { 
     _ForwardIterator __cur = __first; 
     __try 
     { 
      typedef __gnu_cxx::__alloc_traits<_Allocator> __traits; 
      for (; __n > 0; --__n, ++__cur) 
      __traits::construct(__alloc, std::__addressof(*__cur), __x); 
      return __cur; 
     } 
     __catch(...) 
     { 
      std::_Destroy(__first, __cur, __alloc); 
      __throw_exception_again; 
     } 
    } 

    template<typename _ForwardIterator, typename _Size, typename _Tp, 
      typename _Tp2> 
    inline _ForwardIterator 
    __uninitialized_fill_n_a(_ForwardIterator __first, _Size __n, 
          const _Tp& __x, allocator<_Tp2>&) 
    { return std::uninitialized_fill_n(__first, __n, __x); } 
0

Wymagana jest implementacja dowolnej niestandardowej funkcji alokatora, o numerze construct(), aby uzyskać efekt nowego miejsca docelowego (zgodnie z Tabelą 32 - Wymagania alokatora w C++ 03).

Używanie std::uninitialized_fill() (zdefiniowanej jako wykonywanie nowego miejsca w każdym elemencie w zakresie) powinno być w porządku - nawet jeśli jest używany niestandardowy przydział.

+2

Myślę, że pyta, czy można * zastąpić * połączenie z 'construct' z' uninitialized_fill', które jest inne. 'construct' może mieć dodatkowe efekty poza wymaganiami. – Potatoswatter

1

Wolałbym zadzwonić pod numer construct.

Istnieje wiele powodów, dla których warto udostępnić niestandardowy przydział, a lepsza polityka przydziału (przy użyciu puli/stosu) nie jest jedyna.

Widziałem również (i sprzątałem) alokatory debugowania, aby pomóc w śledzeniu nieprawidłowego użycia alokatorów w niestandardowych kontenerach. Możliwe jest również użycie podzielników do śledzenia użycia pamięci, gromadzenia statystyk itp.

Jako taki, construct może (lub nie) mieć dodatkowe efekty poza konstrukcją obiektu, a więc niekoniecznie jest równoważny z proste umieszczenie new.

W moim debugowania podzielnika, nazywając destroy bez nazywa construct na ten adres, zanim wystąpił błąd (a także odzyskiwania pamięci bez nazywa destroy), tak przynajmniej ja polecam bycie konsekwentnym i parowanie construct z destroy i lokowania new z jawnym wywołaniem destruktora.

+0

'destroy', not' destruct' (został ugryziony przez ten jeden kilka razy, w przeciwieństwie do francuskiego, jest asymetryczny :). –

+0

@AlexandreC .: dziękuję, dostaję za nie sprawdzanie ... –

Powiązane problemy