2013-03-12 10 views
5

szukam sposób sformułować klasa posiadająca:Konwersja kontenera STL <T *> do pojemnika <T const *>

  • interfejs użyciu kontenerów STL wskaźników z maksymalną „constness”
  • ale które wewnętrznie mutuje szpiczaste -to obiektów
  • bez dodatkowego okresie czasu w porównaniu do górnego const analogu

w idealnym rozwiązaniem byłoby, aby opracować nie wew kod ra w porównaniu do wersji niestałej, ponieważ const/non-const-ness jest tutaj po prostu pomocą dla programistów.

Oto, co starałem dotąd:

#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_; 
}; 

Jeśli nie ma rozwiązania do konwersji typu, chciałbym alternatywne propozycje dotyczące sposobu formułowania klasy posiadające właściwości opisane.

+0

'T const' nie jest po prostu zamieniany na' T', musisz użyć 'const_cast', ale to jest brzydkie i narusza punkt' const. –

+0

@ Tony Chciałbym przekonwertować 'T' na' T const' chociaż, co jest normalnie możliwe. Następująca kompilacja: 'int x = 2; int const * x_ptr = & x; ' – SimonD

+0

Różne specjalizacje szablonów nie mają związku, są to faktycznie różne typy. – Xeo

Odpowiedz

7

Załóżmy, że przez chwilę można przekonwertować list<T*>& na list<T const *>&. Teraz rozważmy następujący kod:

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

b.push_back("foo"); 

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

To ten sam problem z koncepcyjnego konwersji T** do T const**.

Jeśli chcesz udostępnić dane tylko do odczytu, musisz podać niestandardowy widok, najlepiej używając niestandardowych iteratorów.

Coś jak poniżej.

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 ... kilka nowych rzeczy w moim rozwiązaniu: używając [name] = nazwa_typu T i static_assert ... jakiej wersji standardu to wymaga? (Byłem z dala od C++ przez jakiś czas) – SimonD

+0

Pochodzą z C++ 11. 'używając A = B;' jest takie samo jak 'typedef B A;'; ponieważ jest to tylko kwestia stylistyczna, można ją zastąpić typedef. 'static_assert' powoduje błąd kompilacji, jeśli warunek nie jest spełniony. Nie jest to naprawdę konieczne dla rozwiązania, ale pomaga, ponieważ zapewnia lepszy komunikat o błędzie. Możesz to również opuścić. 'std :: remove_pointer' jest również z C++ 11, ale może być łatwo zaimplementowane bez C++ 11 (https://gist.github.com/rmartinho/5142454). –

+0

Idealne rozwiązanie "skompilowałoby się", wytwarzając ten sam kod zespołu co kod bezterminowy. To rozwiązanie musi zużywać więcej pamięci, ponieważ istnieje nowa klasa o niezerowym rozmiarze. Pośrednie mogą być potencjalnie zoptymalizowane - zależy od tego, czy 'It :: operator *' może zostać wstawiony ...Czy 'const_const_iterator' może pochodzić prywatnie z' To 'aby usunąć poziomy pośredniego i dodatkowego użycia pamięci? – SimonD

1

Nie zwracaj pojemników. Zwróć iteratory.

+1

@nsgulliver - jeśli twoim jedynym narzędziem jest młotek, wszystko wygląda jak gwóźdź. Zazwyczaj lepiej jest odpowiedzieć na ** właściwe ** pytanie, niż na ślepo odpowiedzieć na pytanie, które zostało zadane. –

+0

masz absolutną rację! jednak może być wspaniale mieć trochę więcej szczegółów, aby zmotywować odpowiedź. - nsgulliver 4 min. Temu – nsgulliver

+0

Motywacją do zwracania czegoś więcej niż iteratorów jest to, że kontener może mieć inne przydatne funkcje, takie jak 'size()', 'operator []', 'front()' itd. To jest ból, który trzeba pisać kod na płycie, aby je wszystkie załatać. Oprócz tego, że jest nudny, prawdziwy cel zajęć szybko staje się przesłonięty oceanem drobiazgów. – SimonD

Powiązane problemy