2014-12-25 6 views
12

Ich habe versucht, eine benutzerdefinierte Speicherzuordner zu schaffen, die einen intelligenten Zeiger verwendet. Ich poste den Code nicht, weil er zu groß ist und nicht viel Information hinzufügt. Dann habe ich es mit einer std::vector getestet. Es funktioniert perfekt auf Xcode. Aber als ich versuchte, den gleichen Code in Visual Studio 12 (2013) zu bauen, scheiterte der Build mit dem folgenden Fehler:Speicherzuordner mit benutzerdefinierten Zeigertyp

...vector(873): error C2660: ' std::_Wrap_alloc< my_allocator<int> >::construct ' : function does not take 2 arguments

das Problem ist in push_back Methode:

void push_back(value_type&& _Val) 
    { 
    .... 
     this->_Getal().construct(this->_Mylast, 
      _STD forward<value_type>(this->_Myfirst[_Idx])); 
    .... 
    } 

Die Fehlermeldung ist ein bisschen verwirrend. Wirkliches Problem ist, dass this->_Mylast vom Typ my_allocator<int>::pointer, die eine Smartpointer ist, und konstruieren Methode erwartet int*.

Also, die Frage ist einfach: Was sind die Anforderungen Typen in einem benutzerdefinierten Speicherzuordner verwendet, um den Zeiger? Sollte X::pointer in einen rohen Zeiger konvertierbar sein? Wenn ja, macht es sie ziemlich nutzlos.

Eigentlich würde ich diese Zeile Code erwarten aussehen:

this->_Getal().construct(addressof(*(this->_Mylast)), 
      _STD forward<value_type>(this->_Myfirst[_Idx])); 

Lasst uns versuchen, eine Antwort in C++ Standard zu finden, die sagt:

[17.6.3.5-5] An allocator type X shall satisfy the requirements of CopyConstructible (17.6.3.1). The X::pointer , X::const_pointer , X::void_pointer , and X::const_void_pointer types shall satisfy the requirements of NullablePointer (17.6.3.3). No constructor, comparison operator, copy operation, move operation, or swap operation on these types shall exit via an exception. X::pointer and X::const_pointer shall also satisfy the requirements for a random access iterator (24.2)

Wenn wir einen Blick auf NullablePointer nehmen reqs, fügen sie einige andere Anforderungen:

[17.6.3.3] A NullablePointer type is a pointer-like type that supports null values. A type P meets the requirements of NullablePointer if:
(1.1) — P satisfies the requirements of EqualityComparable, DefaultConstructible, CopyConstructible, CopyAssignable, and Destructible...

Wenn ich Random Access Iterator re überprüfen Ich finde auch keine explizite Erwähnung des Castings für einen rohen Zeiger. Aber an einigen Stellen wird der Ansatz mit addressof verwendet (z. B. 24.2.1-5).

Auch ist es nicht die einzige Stelle in Microsofts std::vector Implementierung, wo X::pointer und rohe Zeiger als gleich angenommen werden. Ich frage mich, was ich vermisse?

EDIT: Ich werde ein Stück my_allocator deffinition hier hinzufügen:

class my_allocator 
{ 
public: 

typedef std::size_t   size_type; 
typedef std::ptrdiff_t  difference_type; 
typedef my_ptr<T>   pointer; 
typedef my_ptr<const T>  const_pointer; 
typedef T&     reference; 
typedef const T&    const_reference; 
typedef T     value_type; 
typedef my_ptr<void>   void_pointer; 
typedef my_ptr<const void> const_void_pointer; 

<constructors> 

pointer allocate(size_type n, const_void_pointer = nullptr); 
void deallocate(const pointer& ptr, size_type elements_num); 
}; 
+1

Können Sie uns alle Typdefinitionen in Ihrem Allokator zur Verfügung stellen? Z.B. 'const_pointer' und so weiter? (Es würde mich absolut nicht überraschen, wenn die Standardbibliothek von VC++ nicht vollständig konform ist) – Columbo

+0

Wahrscheinlich ist die VC++ Standardbibliothek fehlerhaft. Ich dachte, dass die Konstruktion von 'std'-Objekten über' allocator_traits_construct' anstatt direkt 'allocator :: construct' erfolgen sollte. – Walter

+0

@Columbo, Ich habe den gewünschten Code hinzugefügt. – Maxym

Antwort

13

Um dieses Problem zu lösen, habe ich eine to_raw_pointer Funktion, die auf jedem „fancy Zeiger“ arbeiten geschieht, die operator->() implementiert. Sie können es in der libc++ implementation finden.

Hier ist sie:

template <class _Tp> 
inline _LIBCPP_INLINE_VISIBILITY 
_Tp* 
__to_raw_pointer(_Tp* __p) _NOEXCEPT 
{ 
    return __p; 
} 

template <class _Pointer> 
inline _LIBCPP_INLINE_VISIBILITY 
typename pointer_traits<_Pointer>::element_type* 
__to_raw_pointer(_Pointer __p) _NOEXCEPT 
{ 
    return _VSTD::__to_raw_pointer(__p.operator->()); 
} 

, indem es das operator->() auf unkonventionelle Weise aufrufen. Dieser Operator muss entweder einen anderen operator->() aufrufen oder einen echten Zeiger zurückgeben. Die Überladung für reale Zeiger bricht die Rekursion mit einer Identitätsfunktion. So würde dies verwendet werden, wie:

this->_Getal().construct(__to_raw_pointer(this->_Mylast), 
      _STD forward<value_type>(this->_Myfirst[_Idx])); 

construct angegeben ist einen echten Zeiger zu nehmen, keine Phantasie-Zeiger. Und es gibt keine implizite Umwandlung von Phantasiezeigern zu echten Zeigern. Der Container muss etwas wie to_raw_pointer oder addressof verwenden.

Der Container muss auch construct über allocator_traits aufrufen, anstatt ihn wie gezeigt direkt auf dem gespeicherten Zuordner aufzurufen. Dies ermöglicht es construct, von allocator_traits "voreingestellt" zu werden, im Gegensatz zu der Anforderung, dass der Zuweiser construct implementieren muss.

Gegenwärtig erfordern sowohl operator*() als auch operator->() im Allgemeinen, dass der ausgefallene Zeiger vor dem Aufruf dieses Operators nicht null ist. Ich gehe jedoch davon aus, dass diese Anforderung in Zukunft für operator->() gelockert wird.

aktualisieren

war ich ein bisschen in der Eile, wenn ich die oben geschrieben habe. Jetzt, wo ich die Zeit habe, würde ich die vollständigen Anforderungen an die allocator::pointer Typen einschließen. Beim erneuten Lesen der Frage sehe ich jedoch, dass Maxym in der Frage schon gute Arbeit geleistet hat, also werde ich sie hier nicht wiederholen.

Das einzige, was in der std ist, ist aber nicht ganz klar, ist die implizite und explizite Konvertierungen zwischen den vier Zeigertypen: pointer, const_pointer, void_pointer und const_void_pointer:

implicit allocator pointer conversions: 
+--------------------------------------+ 
| pointer  --> const_pointer  | 
| | \    |   | 
| |  ---------  |   | 
| \|/    _\| \|/   | 
| void_pointer --> const_void_pointer | 
+--------------------------------------+ 


explicit allocator pointer conversions: 
+--------------------------------------+ 
| pointer   const_pointer  | 
| /|\     /|\   | 
| |     |   | 
| |     |   | 
| void_pointer  const_void_pointer | 
+--------------------------------------+ 

Das heißt, Sie können implizit von const in const und von nicht void in void konvertieren, und Sie können explizit konvertieren von void in nicht void. Aber es gibt keine Möglichkeit für einen Behälter const_cast (const -ness wegwerfen) von allocator::const_pointer oder allocator::const_void_pointer. Sobald der Container const geht, kann er nie mehr zurückkommen.

+1

Cool, aber es ist eigentlich das gleiche wie addressof (* (this -> _ Mylast)), weil die Zeiger-Indirektion sowieso vom Standard benötigt wird. Ich versuche herauszufinden, ob ich etwas falsch mache (weil ich den C++ - Standard falsch interpretiere), oder es ist ein Fehler in der VS STL-Implementierung. – Maxym

+0

Also, bin ich richtig zu schließen, dass es ein Fehler in der VC++ STL-Implementierung ist? (Da X :: Zeiger auf rohen Zeiger und umgekehrt ist nicht erlaubt). – Maxym

+0

Ja: 'construct' wird angegeben, um einen echten Zeiger und keinen schicken Zeiger zu verwenden. Und es gibt keine implizite Umwandlung von Phantasiezeigern zu echten Zeigern. Der Container muss etwas wie "to_raw_pointer" oder "addressof" verwenden. –

Verwandte Themen