2013-03-12 7 views
5

ich nach einem Weg suchen, die eine Klasse zu formulieren:Konvertieren von STL-Container <T *> zu Container <T const *>

  • ein Schnittstelle STL-Container von Zeigern mit maximaler ‚Konstantheit‘ mit
  • aber dem intern die spitzen mutiert -zu-Objekte
  • ohne zusätzlichen Laufzeitaufwand im Vergleich zu einem nicht konstanten analogen

Idealerweise wäre die Lösung ohne ext kompiliert Ra-Code gegenüber der nicht-const-Version, da const/non-const-ness nur eine Hilfe für Programmierer ist.

Hier ist, was ich versucht habe, so weit:

#include <list> 
#include <algorithm> 

using namespace std; 
typedef int T; 

class C 
{ 
public: 
    // Elements pointed to are mutable, list is not, 'this' is not - compiles OK 
    list<T *> const & get_t_list() const { return t_list_; } 

    // Neither elements nor list nor' this' are mutable - doesn't compile 
    list<T const *> const & get_t_list2() const { return t_list_; } 

    // Sanity check: T const * is the problem - doesn't compile 
    list<T const *> & get_t_list3() { return t_list_; } 

    // Elements pointed to are immutable, 'this' and this->t_list_ are 
    // also immutable - Compiles OK, but actually burns some CPU cycles 
    list<T const *> get_t_list4() const { 
     return list<T const *>(t_list_.begin() , t_list_.end()); 
    } 

private: 
    list<T *> t_list_; 
}; 

Wenn es keine Lösung für die Typumwandlung ist, würde ich alternative Vorschläge mag, wie beschrieben eine Klasse mit den Eigenschaften zu formulieren.

+0

'T const' ist nicht einfach in' T' konvertierbar, man muss 'const_cast' verwenden, aber das ist hässlich und verletzt den Punkt von' const. –

+0

@Tony Ich möchte "T" in "T const" umwandeln, was normalerweise möglich ist. Die folgenden Kompilierungen: 'int x = 2; int const * x_ptr = & x; ' – SimonD

+0

Verschiedene Template-Spezialisierungen sind nicht verwandt, sie sind effektiv unterschiedliche Typen. – Xeo

Antwort

7

Nehmen wir an, Sie können list<T*>& in list<T const *>& konvertieren. Betrachten wir nun den folgenden Code:

list<char*> a; 
list<char const*>& b = a; 

b.push_back("foo"); 

a.front()[0] = 'x'; // oops mutating const data 

Es ist das gleiche konzeptionelles Problem mit T**-T const** konvertieren.

Wenn Sie schreibgeschützten Zugriff auf die zugrunde liegenden Daten bereitstellen möchten, müssen Sie eine benutzerdefinierte Ansicht bereitstellen, möglicherweise unter Verwendung von benutzerdefinierten Iteratoren.

Etwas wie das Folgende.

template <typename It> 
class const_const_iterator { 
private: 
    using underlying_value_type = typename std::iterator_traits<It>::value_type; 

    static_assert(std::is_pointer<underlying_value_type>(), 
        "must be an iterator to a pointer"); 

    using pointerless_value_type = typename std::remove_pointer<underlying_value_type>::type; 

public: 
    const_const_iterator(It it) : it(it) {} 

    using value_type = pointerless_value_type const*; 

    value_type operator*() const { 
     return *it; // *it is a T*, but we return a T const*, 
        // converted implicitly 
        // also note that it is not assignable 
    } 

    // rest of iterator implementation here 
    // boost::iterator_facade may be of help 

private: 
    It it; 
}; 

template <typename Container> 
class const_const_view { 
private: 
    using container_iterator = typename Container::iterator; 

public: 
    using const_iterator = const_const_iterator<container_iterator>; 
    using iterator = const_iterator; 

    const_const_view(Container const& container) : container(&container) {} 

    const_iterator begin() const { return iterator(container->begin()); } 
    const_iterator end() const { return iterator(container->end()); } 

private: 
    Container const* container; 
} 
+0

Wow ... mehrere neue Dinge für mich in Ihrer Lösung: mit [Name] = Typname T und static_assert ... welche Version des Standards braucht das? (Ich war eine Weile von C++ entfernt) – SimonD

+0

Sie stammen aus C++ 11. 'mit A = B;' ist das gleiche wie 'typedef B A;'; Da es nur eine stilistische Angelegenheit ist, können Sie es durch typedef ersetzen. 'static_assert' verursacht einen Kompilierungsfehler, wenn die Bedingung nicht erfüllt ist. Es ist nicht wirklich notwendig für die Lösung, aber es hilft, weil es eine bessere Fehlermeldung bietet. Du kannst es auch weglassen. 'std :: remove_pointer' stammt ebenfalls von C++ 11, kann aber ohne C++ 11 (https://gist.github.com/rmartinho/5142454) einfach implementiert werden. –

+0

Die ideale Lösung würde "kompilieren" und den gleichen Assembler-Code wie Const-less-Code erzeugen. Diese Lösung muss mehr Arbeitsspeicher verwenden, da eine neue Klasse mit einer Größe ungleich null vorhanden ist. Die Indirektionen können möglicherweise weg optimiert werden - hängt davon ab, ob 'It :: operator *' inline ...Konnte "const_const_iterator" privat von 'It' abgeleitet werden, um Ebenen der Indirektion und zusätzlichen Speicherverbrauch zu entfernen? – SimonD

1

Behälter nicht zurückgeben. Iteratoren zurückgeben.

+1

@nsgulliver - wenn dein einziges Werkzeug ein Hammer ist, sieht alles wie ein Nagel aus. Es ist normalerweise besser, die ** richtige ** Frage zu beantworten, als blind auf die Frage zu antworten. –

+0

Sie haben absolut Recht! Es kann jedoch gut sein, ein bisschen mehr Details zu haben, um die Antwort zu motivieren. - nsgulliver Vor 4 Minuten – nsgulliver

+0

Die Motivation für die Rückgabe von etwas mehr als Iteratoren ist, dass der Container andere nützliche Funktionen wie 'size()', 'operator []', 'front()' usw. haben kann. Es ist ein Schmerz zu schreiben Vorabcode, um sie alle zu patchen. Der wahre Zweck einer Klasse wird nicht nur mühsam zu schreiben, sondern wird auch schnell in einem Ozean von Nebensächlichkeiten verdunkelt. – SimonD

Verwandte Themen