2015-02-14 18 views
8

C++ standardowe kontenery i podzielniki zapewnić typedefs dla typu wskaźnik używany przez kontener, tj:typy wskaźnik klienta i kontener/typedefs podzielnika

typename std::vector<T>::pointer 
typename std::vector<T>::const_pointer 

Rzeczywisty typ wskaźnik używany do tworzenia typedef jest określana poprzez std::allocator_traits

typedef typename std::allocator_traits<Allocator>::pointer pointer; 

Ponieważ każdy pojemnik ma również value_type typedef, przypuszczalnie cel pointer typedef jest dla jakiejś dziwnej sytuacji, w której wskaźnik typu stosowane jest coś inny niż value_type*. Nigdy osobiście nie widziałem przypadek użycia czegoś takiego, ale przypuszczam, że komitet standardów chciał zapewnić możliwość korzystania z niestandardowych typów wskaźników z pojemnikami.

Problem polega na tym, że wydaje się to być niespójne z definicjami przewidzianymi dla funkcji w std::allocator_traits. W szczególności, w std::allocator_traits mamy funkcję construct, który jest zdefiniowany jako:

template <class T, class... Args> 
static void construct(Alloc& a, T* p, Args&&... args); 

... co tylko wywołuje a.construct(p, std::forward<Args>(args)...)

Należy jednak pamiętać, że ta funkcja sprawia żadnych elementów umożliwiających niestandardowego typu wskaźnika. Parametr p jest zwykłym natywnym wskaźnikiem.

Więc dlaczego nie jest definicja ta funkcja coś takiego:

template <class... Args> 
static void construct(Alloc& a, typename Alloc::pointer p, Args&&... args); 

Wydaje się bez tego, pojemniki używane std::allocator_traits<Alloc>::construct zawiedzie, jeżeli są stosowane z podzielnik, który definiuje pewne niestandardowy typ wskaźnika.

Co się tutaj dzieje? A może nie rozumiem celu, jakim jest posiadanie pointer maszynopisu na pierwszym miejscu?

+0

'Konstrukt' jest/musi być przypięty do elementu * typ *. podobnie z 'destroy'. "allocate" i "dealocate" nie są. Zarówno 'allocate', jak i' dealokacja 'odnoszą się do typu' wskaźnika ', który kontrolujesz. * Żadna z tych funkcji nie dotyczy faktycznego obiektu * konstrukcji * i * zniszczenia *, a zatem nie ma żadnego nakazu przypinania do typu 'T'. Zarówno 'construct' i' destroy', jednak * do *. – WhozCraig

+0

@dyp ':: std :: addressof (* p)', a raczej;) –

+0

Cała idea 'pointer! = Value_type *' wydaje się być (rodzaj) wprowadzona w C++ 11. O ile rozumiem, w C++ 03 implementatorzy kontenerów mogliby przyjmować 'wskaźnik == typ_wartości *'. Obecnie szukam propozycji o tym, dlaczego została zmieniona. Wydaje się, że ma to coś wspólnego z dokumentem koncepcyjnym alokatora (N2654) i potencjalnie z modelem podzielonego zakresu (N2554). – dyp

Odpowiedz

6

Ta dychotomia jest celowa i nie stanowi problemu. Funkcja construct członkiem jest zazwyczaj realizowane tak:

template <class U, class ...Args> 
void 
construct(U* p, Args&& ...args) 
{ 
    ::new(static_cast<void*>(p)) U(std::forward<Args>(args)...); 
} 

Tj przekazuje do umieszczenia new, co z kolei ma ten podpis:

void* operator new (std::size_t size, void* ptr) noexcept; 

Więc ostatecznie potrzebujesz „prawdziwe” wskaźnik, aby zadzwonić do lokowania nowych. Aby przekazać typ obiektu, który należy skonstruować, wystarczy przekazać tę informację w typie wskaźnika (na przykład U*).

Dla symetrii destroy również formułowane w kategoriach rzeczywistego wskaźnika i jest zazwyczaj realizowane w taki sposób:

template <class U> 
void 
destroy(U* p) 
{ 
    p->~U(); 
} 

Głównym przypadek użycia dla wskaźnika „fantazyjny” jest umieszczenie obiektów w pamięci współdzielonej. Do tego celu zwykle używana jest klasa o nazwie offset_ptr, a także można utworzyć alokator, aby alokować i zwalniać pamięć, o której wspomina offset_ptr. A zatem funkcje allocator_traits i allocator i działają w ruchu pod względem pointer zamiast value_type*.

Pojawia się zatem pytanie: Jeśli masz numer pointer i potrzebujesz T*, co robisz?

Istnieją dwie techniki jestem świadomy do tworzenia T* z pointer p

1.std::addressof(*p);

Kiedy dereference o pointer p, to musi skutkować lwartością zgodnie z normą. Jednak byłoby miło móc rozluźnić to wymaganie (np. Rozważenie pointer zwrócenia referencji proxy, takiej jak vector<bool>::reference). std::addressof został określony zwrócić T* do dowolnego lwartości:

template <class T> T* addressof(T& r) noexcept; 

2.to_raw_pointer(p); // where:

template <class T> 
inline 
T* 
to_raw_pointer(T* p) noexcept 
{ 
    return p; 
} 

template <class Pointer> 
inline 
typename std::pointer_traits<Pointer>::element_type* 
to_raw_pointer(Pointer p) noexcept 
{ 
    return ::to_raw_pointer(p.operator->()); 
} 

ten nazywa pointer „s operator->(), które będą bezpośrednio zwrócić T* lub przekazać coś, co albo bezpośrednio lub pośrednio zwracają T*. Wszystkie pointer typy powinny obsługuje operator->(), nawet jeśli odwołuje się do bool. Wadą tej techniki jest to, że obecnie nie jest wymagane wywoływanie operator->(), chyba że dereferencable pointer. To ograniczenie powinno zostać zniesione w standardzie.

W C++ 14 typ powrotu drugiego przeciążenia (właściwie właściwie oba przeciążenia) można wygodnie zastąpić przez auto.


Jeśli masz T* i chcą wybudować pointer, masz pecha. Nie ma przenośnego sposobu na konwersję w tym kierunku.


Należy również pamiętać, to tangentially related LWG issue o funkcji vector::data() użytkownika typ zwracany. Odbił się między value_type*, pointer iz powrotem i jest obecnie (i celowo) value_type*.

+0

Co miałem na myśli to, że ten wymóg powinien być złagodzony w standardzie. Dzięki, wyjaśnię mój niedbały język. –

+0

* "Ostatecznie potrzebujesz" prawdziwego "wskaźnika, aby zadzwonić do nowego miejsca docelowego."* Ale na pewno, * alokator * powinien wiedzieć, jak uzyskać' T * 'z' wskaźnika '. Dlaczego więc nie jest to obowiązkiem alokatora wykonanie tej konwersji/wyodrębnienia? – dyp

+1

To byłoby niepotrzebne oprócz API alokatora. Rzeczywiście, alokatory C++ 98/03 faktycznie miały takie API w funkcji członkowskiej 'address', a to wymaganie zostało po prostu porzucone na korzyść bardziej użytecznego samodzielnego narzędzia' std :: addressof() '. –