2013-01-21 13 views
9

Rozważam this question dla metod klasy const i non const. Preferowaną odpowiedzią jest efektywny C++ autorstwa Scotta Meyersa, gdzie metoda niestanowiąca stałych jest zaimplementowana w kategoriach metody const.Usuwanie powielania kodu z metod const i non const, które zwracają iteratory

Poszerzenie tego dalej, w jaki sposób można zmniejszyć duplikację kodu, jeśli metody zwracają iteratory zamiast odwołań? Modyfikowanie przykład w połączonej pytanie:

class X 
{ 
    std::vector<Z> vecZ;  
public: 
    std::vector<Z>::iterator Z(size_t index) 
    { 
     // ... 
    } 
    std::vector<Z>::const_iterator Z(size_t index) const 
    { 
     // ... 
    } 
}; 

nie mogę wdrożyć metody non-const pod względem sposobu const, ponieważ nie jest możliwe do konwersji bezpośrednio z const_iterator do iteratora bez korzystania z dystansu()/advance() technika.

W tym przykładzie, ponieważ używamy wektora std :: vector jako kontenera, może być możliwe rzutowanie z const_iterator na iterator, ponieważ mogą one zostać zaimplementowane jako wskaźniki. Nie chcę na tym polegać. Czy istnieje bardziej ogólne rozwiązanie?

+0

Ta funkcja jest albo trywialna, albo powinna być zaimplementowana jako szablon funkcji algorytmu, który sprawia, że ​​funkcja członka znowu staje się trywialna. – ipc

+0

W tym przypadku obie funkcje mogą być 'return vecZ.begin() + index'. Lub, jeśli absolutnie musisz je zmienić, wersja const może być 'return vecZ.cbegin() + index'. Patrz, nie ma duplikacji! –

Odpowiedz

2

wierzę, że jest to możliwe tylko z pomocnika

typedef int Z; 

class X 
{ 
    std::vector<Z> vecZ;  
public: 
    std::vector<Z>::iterator foo(size_t index) 
    { 
     return helper(*this); 
    } 
    std::vector<Z>::const_iterator foo(size_t index) const 
    { 
     return helper(*this); 
    } 

    template <typename T> 
    static auto helper(T& t) -> decltype(t.vecZ.begin()) 
    { 
     return t.vecZ.begin(); 
    } 
}; 

EDIT samo może być realizowany bez C++ 11

template <typename T> 
struct select 
{ 
    typedef std::vector<Z>::iterator type; 
}; 
template <typename T> 
struct select<const T&> 
{ 
    typedef std::vector<Z>::const_iterator type; 
}; 

template <typename T> 
static 
typename select<T>::type 
helper(T t) 
{ 
    return t.vecZ.begin(); 
} 

Ale dobrze, myślę, że powinien Zastanów się dwa razy zanim użyjesz tego approcach

+0

To wygląda na użyteczne podejście, chociaż nie będę miał kompilatora C++ 11 dla mojej platformy docelowej w przewidywalnej przyszłości. –

+1

@SimonElliott, patrz edycja – Lol4t0

+0

Ma to ten sam problem, co w [próba trzy dni temu] (http://stackoverflow.com/questions/14401017/call-nonconst-member-version-from-const) w celu ograniczenia duplikacji kodu - faktycznie powoduje więcej kodu. Gdzie jest przewaga? –

5

Istnieje kilka sztuczek, których można użyć. You can turn a const_iterator into an iterator if you have non-const access to the container. (co zrobić):

std::vector<Z>::iterator Z(size_t index) 
{ 
    std::vector<Z>::const_iterator i = static_cast<const X&>(*this).Z(index); 
    return vecZ.erase(i, i); 
} 

Można też to zrobić:

std::vector<Z>::iterator Z(size_t index) 
{ 
    return std::next(vecZ.begin(), std::distance(vecZ.cbegin(), static_cast<const X&>(*this).Z(index))); 
} 

Ani są szczególnie eleganckie. Dlaczego nie mamy const_iterator_cast jak const_pointer_cast? Może powinniśmy.

EDIT, Brakowało mi najbardziej oczywistego i eleganckiego rozwiązania, ponieważ próbowałem użyć metody const z metody niestałej. Tu robię coś przeciwnego:

std::vector<Z>::const_iterator Z(size_t index) const 
{ 
    return const_cast<X&>(*this).Z(index); 
} 

Jesteś dereferencji wynik const_cast jednak to nie jest niezdefiniowane zachowanie tak długo, jak nie-const Z nie zmienia żadnych danych w X. W przeciwieństwie do innych 2 podanych przeze mnie rozwiązań, jest to jedna z nich, którą mogę wykorzystać w praktyce.

+1

Powinieneś zawsze zaimplementować wersję const i const. Zapobiega to przypadkowemu mutowaniu tej implementacji. –

+0

@CharlesBeattie Zapobiega przypadkowemu mutowaniu tego "wykonania" przez implementację? Zapobiega to przypadkowemu mutowaniu 'this' w Twojej niestanowiącej metody. Ale już napisałem to zastrzeżenie. To UB, jeśli modyfikujesz 'X' w niestałym' Z' i wywołujesz const "Z" z const 'Z'. Zgadzam się, że jest to zła praktyka, zwłaszcza jeśli ludzie mieszają się z 'Z' i ktoś może dodać do niego jakiś kod modyfikujący. – David

+1

Tak, myślę, że twoje myślenie odzwierciedla moje w tej kwestii. Chciałem dodać dodatkowy nacisk, że ostatnim rozwiązaniem jest zła praktyka (choć jest to praktyczne rozwiązanie). –

Powiązane problemy