2015-02-14 17 views
8

C++ contenitori standard e allocatori forniscono typedef per il tipo di puntatore utilizzato dal contenitore, cioè:tipi di puntatore personalizzata e contenitore/typedef allocatore

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

Il tipo di puntatore effettivo utilizzato per creare il typedef viene determinata tramite std::allocator_traits

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

Poiché ogni contenitore ha anche un typedef value_type, presumibilmente lo scopo della pointer typedef è per qualche strana situazione in cui il tipo di puntatore utilizzato è qualcosa altro rispetto a value_type*. Non ho mai visto personalmente un caso d'uso per qualcosa del genere, ma suppongo che il comitato degli standard volesse fornire la possibilità di utilizzare tipi di puntatori personalizzati con contenitori.

Il problema è che questo sembra essere in contrasto con le definizioni previste le funzioni std::allocator_traits. In particolare, in std::allocator_traits abbiamo la funzione di construct, che è definito come:

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

... che ha appena chiama a.construct(p, std::forward<Args>(args)...)

Ma si noti che questa funzione non fa disposizioni per un tipo di puntatore personalizzato. Il parametro p è un semplice puntatore nativo.

Quindi, perché non è la definizione di questa funzione qualcosa come:

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

Sembra senza questo, i contenitori che hanno usato std::allocator_traits<Alloc>::construct fallirebbe se utilizzato con un allocatore che definisce un certo tipo di puntatore personalizzato.

Allora, che cosa sta succedendo qui? O sto fraintendendo lo scopo di avere pointer typedefs in primo luogo?

+0

'construct' è/deve essere aggiunto all'elemento * tipo *. allo stesso modo con 'destroy'. 'allocate' e' deallocate' non lo sono. Sia 'allocate' che' deallocate' si riferiscono al tipo 'pointer' che stai ispezionando. * Né * di quelle funzioni si occupa dell'oggetto reale * costruzione * e * distruzione *, e quindi non hanno alcuna garanzia di pinning per digitare 'T'. Sia 'construct' che' destroy', tuttavia, * do *. – WhozCraig

+0

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

+0

L'intera idea di 'pointer! = Value_type *' sembra essere stata (sorta di) introdotta in C++ 11. Per quanto ho capito, in C++ 03, gli implementatori di container potevano assumere 'pointer == value_type *'. Attualmente sto cercando le proposte sul perché è stato cambiato. Sembra avere qualcosa a che fare con la carta dei concetti allocator (N2654) e potenzialmente il modello di allocatore dell'ambito (N2554). – dyp

risposta

6

Questa dicotomia è propositivo, e non presenta un problema. La funzione construct membro è tipicamente implementato in questo modo:

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

Vale a dire in avanti per il posizionamento new, che a sua volta ha questa firma:

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

Quindi in ultima analisi, è necessario un puntatore "reale" per chiamare nuova collocazione. E per trasmettere il tipo di oggetto che deve essere costruito, ha senso passare quell'informazione nel tipo di puntatore (ad es. U*).

Per simmetria, destroy sarebbe formulata in termini di un puntatore reale e viene tipicamente implementato in questo modo:

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

Il caso di utilizzo principale per il "puntatore fantasia" è quello di posizionare oggetti nella memoria condivisa. Una classe denominata offset_ptr viene in genere utilizzata per questo scopo e può essere creato un allocatore per allocare e deallocare la memoria a cui fa riferimento offset_ptr. E quindi il numero allocator_traits e allocatorallocate e deallocate funziona come traffico in termini di pointer anziché value_type*.

Quindi la domanda sorge spontanea: se hai uno pointer e hai bisogno di un T*, cosa fai?

ci sono due tecniche io sappia per la creazione di un T* da un pointer p

1.std::addressof(*p);

Quando si dereference un pointer p, è necessario portare a un lvalue secondo lo standard. Tuttavia sarebbe bello essere in grado di attenuare questo requisito (ad esempio, considerare pointer restituendo un riferimento proxy come vector<bool>::reference). std::addressof è specificato per restituire un T* a qualsiasi lvalue:

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->()); 
} 

Ciò richiede un s pointer' operator->(), che sia tornare direttamente un T* o inoltrare a qualcosa che sia volontà restituire direttamente o indirettamente un T*. Tutti i tipi pointerdovrebbe supporto operator->(), anche se si fa riferimento a un bool. Uno svantaggio di questa tecnica è che al momento non è necessario chiamare operator->() a meno che il parametro pointer sia dereferenziabile. Tale restrizione dovrebbe essere revocata nello standard.

In C++ 14 il tipo di ritorno del secondo sovraccarico (beh in realtà entrambi i sovraccarichi), può essere comodamente sostituito con auto.


Se si dispone di un T* e desidera costruire una pointer, siete fuori di fortuna. Non esiste un modo portatile per convertire in questa direzione.


noti inoltre questo tangentially related LWG issue sul tipo di ritorno della funzione vector::data() membro. È rimbalzato tra value_type*, pointer e indietro, ed è attualmente (e intenzionalmente) value_type*.

+0

Quello che intendevo era che questo requisito dovrebbe essere rilassato nello standard. Grazie, chiarirò il mio linguaggio incurante. –

+0

* "Quindi, in definitiva, è necessario un puntatore" reale "per chiamare il nuovo posizionamento."* Ma sicuramente, * l'allocatore * dovrebbe sapere come ottenere un' T * 'da un' puntatore' .. quindi perché non è la responsabilità dell'allocatore fare quella conversione/estrazione? – dyp

+1

Quello sarebbe un inutile Oltre all'API dell'allentatore, gli allocatori C++ 98/03 avevano effettivamente una tale API nella funzione membro 'address', e tale requisito veniva semplicemente abbandonato a favore dell'utilità standalone più utile' std :: addressof() '. –

Problemi correlati