Powiedzmy, że mam następujące miejsce, w class X
którym chcę zwrócić dostęp do członka wewnętrznego:
class Z
{
// details
};
class X
{
std::vector<Z> vecZ;
public:
Z& Z(size_t index)
{
// massive amounts of code for validating index
Z& ret = vecZ[index];
// even more code for determining that the Z instance
// at index is *exactly* the right sort of Z (a process
// which involves calculating leap years in which
// religious holidays fall on Tuesdays for
// the next thousand years or so)
return ret;
}
const Z& Z(size_t index) const
{
// identical to non-const X::Z(), except printed in
// a lighter shade of gray since
// we're running low on toner by this point
}
};
Te dwie funkcje składowe X::Z()
i X::Z() const
mają identyczny kod wewnątrz szelki. Jest to zduplikowany kod i może powodować problemy z obsługą długich funkcji o złożonej logice .
Czy istnieje sposób na uniknięcie tego powielania kodu?
Odpowiedzi:
Szczegółowe wyjaśnienie znajduje się w tytule „Unikaj powielania w funkcjach innych
const
niż elementyconst
członkowskie” na s. 9. 23, w punkcie 3 „Używaj,const
kiedy to możliwe”, w Effective C ++ , 3d wyd. Scott Meyers, ISBN-13: 9780321334879.Oto rozwiązanie Meyersa (uproszczone):
Dwie rzutowania i wywołanie funkcji mogą być brzydkie, ale są poprawne. Meyers ma dokładne wyjaśnienie, dlaczego.
źródło
get()const
zwraca coś, co zostało zdefiniowane jako obiekt stały, to w ogóle nie powinna istnieć wersja nie stałaget()
. W rzeczywistości moje myślenie o tym zmieniło się z czasem: rozwiązanie szablonu jest jedynym sposobem uniknięcia duplikacji i uzyskania stałej poprawności sprawdzanej przez kompilator, więc osobiście nie używałbym więcejconst_cast
w celu uniknięcia powielania kodu, wybrałbym skopiowany kod do szablonu funkcji lub pozostawiając go zduplikowanym.template<typename T> const T& constant(T& _) { return const_cast<const T&>(_); }
itemplate<typename T> T& variable(const T& _) { return const_cast<T&>(_); }
. Następnie możesz zrobić:return variable(constant(*this).get());
Tak, można uniknąć duplikacji kodu. Musisz użyć funkcji const członka, aby mieć logikę, a funkcja non-const członka wywoła funkcję const członka i ponownie wyśle wartość zwracaną do odwołania non-const (lub wskaźnika, jeśli funkcje zwrócą wskaźnik):
UWAGA: Ważne jest, aby NIE umieszczać logiki w funkcji non-const, a funkcja const wywoła funkcję non-const - może to spowodować niezdefiniowane zachowanie. Powodem jest to, że instancja klasy stałej jest rzutowana jako instancja niestała. Niepodstawowa funkcja członkowska może przypadkowo zmodyfikować klasę, co spowoduje, że stany standardu C ++ spowodują niezdefiniowane zachowanie.
źródło
C ++ 17 zaktualizował najlepszą odpowiedź na to pytanie:
Ma to takie zalety, że:
volatile
przez przypadek, alevolatile
jest rzadkim kwalifikatorem)Jeśli chcesz przejść pełną trasę dedukcyjną, możesz to osiągnąć, korzystając z funkcji pomocnika
Teraz nie możesz nawet zepsuć się
volatile
, a wygląda to na użycieźródło
f()
zwracaT
zamiastT&
.f()
zwrotówT
nie chcemy mieć dwóch przeciążeń,const
wystarczy sama wersja.shared_ptr
. Więc tak naprawdę potrzebowałem czegoś,as_mutable_ptr
co wygląda prawie identycznie jakas_mutable
powyżej, z tym wyjątkiem, że bierze i zwraca ashared_ptr
i używastd::const_pointer_cast
zamiastconst_cast
.T const*
wiązałoby się toT const* const&&
raczej z wiązaniem niż zT const* const&
(przynajmniej w moich testach tak się stało). Musiałem dodać przeciążenie dlaT const*
jako typu argumentu dla metod zwracających wskaźnik.Myślę, że rozwiązanie Scotta Meyersa można ulepszyć w C ++ 11 za pomocą funkcji pomocnika tymczasowego. To sprawia, że intencja jest o wiele bardziej oczywista i może być ponownie wykorzystana dla wielu innych osób pobierających.
Tej funkcji pomocnika można użyć w następujący sposób.
Pierwszym argumentem jest zawsze ten wskaźnik. Drugi to wskaźnik do funkcji członka, którą należy wywołać. Następnie można przekazać dowolną liczbę dodatkowych argumentów, aby można je było przekazać do funkcji. Wymaga to C ++ 11 z powodu różnych szablonów.
źródło
std::remove_bottom_const
iśćstd::remove_const
.const_cast
. MożeszgetElement
sam stworzyć szablon i użyć cechy tego typu w środku dompl::conditional
typów, których potrzebujesz, takich jakiterator
s lubconstiterator
s, jeśli to konieczne. Prawdziwy problem polega na tym, jak wygenerować stałą wersję metody, gdy nie można szablonować tej części podpisu?std::remove_const<int const&>
isint const &
(usuńconst
kwalifikacje na najwyższym poziomie ), stąd gimnastykaNonConst<T>
w tej odpowiedzi. Domniemanystd::remove_bottom_const
może usunąćconst
kwalifikację najniższego poziomu i zrobić dokładnie to, coNonConst<T>
tutaj:std::remove_bottom_const<int const&>::type
=>int&
.getElement
jest przeciążone. Wówczas wskaźnika funkcji nie można rozwiązać bez jawnego podania parametrów szablonu. Czemu?likeConstVersion(TObj const* obj, TConstReturn (TObj::*memFun)(TArgs...) const, TArgs&&... args) { return const_cast<typename NonConst<TConstReturn>::type>((obj->*memFun)(std::forward<TArgs>(args)...)); }
Wypełnij: gist.github.com/BlueSolei/bca26a8590265492e2f2760d3cefcf83Trochę bardziej gadatliwy niż Meyers, ale mógłbym to zrobić:
Metoda prywatna ma niepożądaną właściwość polegającą na tym, że zwraca nie-stałą Z i dla stałej instancji, dlatego jest prywatna. Prywatne metody mogą łamać niezmienniki interfejsu zewnętrznego (w tym przypadku pożądanym niezmiennikiem jest „obiekt stały nie może być modyfikowany poprzez referencje uzyskane przez niego do obiektów, które posiada”).
Zauważ, że komentarze są częścią wzorca - interfejs _getZ określa, że nigdy nie można go wywoływać (poza akcesoriami, oczywiście): i tak nie ma żadnych korzyści, ponieważ jest to 1 dodatkowy znak do wpisania i nie będzie skutkuje mniejszym lub szybszym kodem. Wywołanie metody jest równoważne z wywołaniem jednego z akcesorów za pomocą const_cast, a ty też tego nie chciałbyś zrobić. Jeśli martwisz się, że błędy będą oczywiste (a to uczciwy cel), nazwij to const_cast_getZ zamiast _getZ.
Nawiasem mówiąc, doceniam rozwiązanie Meyersa. Nie mam przeciwko temu filozoficznego sprzeciwu. Osobiście jednak wolę odrobinę kontrolowanego powtarzania i metodę prywatną, którą można wywołać tylko w ściśle ściśle kontrolowanych okolicznościach, niż metodę, która wygląda jak szum linii. Wybierz swoją truciznę i trzymaj się jej.
[Edycja: Kevin słusznie zauważył, że _getZ może chcieć wywołać kolejną metodę (powiedzmy generateZ), która jest wyspecjalizowana w tej samej zasadzie co getZ. W takim przypadku _getZ zobaczyłby const Z i musiałby go const_cast przed powrotem. Nadal jest to bezpieczne, ponieważ akcesorium do płyty kotłowej porządkuje wszystko, ale nie jest szczególnie oczywiste, że jest bezpieczne. Ponadto, jeśli to zrobisz, a następnie zmienisz generatorZ, aby zawsze zwracał stałą, musisz również zmienić getZ, aby zawsze zwracał stałą, ale kompilator nie powie ci, że to zrobisz.
Ten ostatni punkt dotyczący kompilatora jest również zgodny z zalecanym wzorcem Meyersa, ale pierwszy punkt dotyczący nieoczywistego const_cast nie jest. Podsumowując, myślę, że jeśli _getZ okaże się, że potrzebuje const_cast dla jego wartości zwracanej, wówczas ten wzór traci dużo swojej wartości w porównaniu z wartością Meyersa. Ponieważ ma również wady w porównaniu do Meyersa, myślę, że w tej sytuacji przeszedłbym na jego. Refaktoryzacja od jednego do drugiego jest łatwa - nie wpływa na żaden inny poprawny kod w klasie, ponieważ tylko niepoprawny kod i płyta główna wywołują _getZ.]
źródło
something
w_getZ()
funkcji jest zmienna instancji? Kompilator (lub przynajmniej niektóre kompilatory) będą narzekać, że skoro_getZ()
jest const, to każda zmienna instancji, do której się odwołuje, jest również const. Tak więcsomething
byłby const (byłby typuconst Z&
) i nie mógłby zostać przekonwertowany naZ&
. Z mojego (co prawda nieco ograniczonego) doświadczeniasomething
wynika , że przez większość czasu w takich przypadkach jest to zmienna instancji.const_cast
. Miał być miejscem na kod wymaganym do uzyskania niezmiennego zwrotu z obiektu const, a nie jako miejscem na to , co byłoby w zduplikowanym getterze. Zatem „coś” nie jest tylko zmienną instancji.Ładne pytanie i ładne odpowiedzi. Mam inne rozwiązanie, które nie używa rzutów:
Ma jednak brzydotę polegającą na wymaganiu członu statycznego i konieczności użycia
instance
zmiennej wewnątrz niego.Nie rozważyłem wszystkich możliwych (negatywnych) implikacji tego rozwiązania. Daj mi znać, jeśli w ogóle.
źródło
auto get(std::size_t i) -> auto(const), auto(&&)
. Czemu '&&'? Ach, więc mogę powiedzieć:auto foo() -> auto(const), auto(&&) = delete;
this
słowo kluczowe. Sugeruję,template< typename T > auto myfunction(T this, t args) -> decltype(ident)
że to słowo kluczowe zostanie rozpoznane jako domyślny argument instancji obiektu i pozwoli kompilatorowi rozpoznać, że moja funkcja jest członkiem lubT
.T
zostaną automatycznie wydedukowane na stronie połączenia, która zawsze będzie rodzajem klasy, ale z bezpłatną kwalifikacją cv.const_cast
tym), że umożliwia powrótiterator
iconst_iterator
.static
można to zrobić w zakresie pliku zamiast zakresu klasy. :-)Możesz to również rozwiązać za pomocą szablonów. To rozwiązanie jest nieco brzydkie (ale brzydota jest ukryta w pliku .cpp), ale zapewnia sprawdzanie ciągłości kompilatora i brak duplikacji kodu.
plik .h:
plik .cpp:
Główną wadą, jaką widzę, jest to, że ponieważ cała złożona implementacja metody jest funkcją globalną, musisz albo zdobyć elementy X przy użyciu publicznych metod, takich jak GetVector () powyżej (z których zawsze musi istnieć const i non-const version) lub możesz uczynić tę funkcję przyjazną. Ale nie lubię przyjaciół.
[Edycja: usunięto niepotrzebne dołączenie cstdio dodane podczas testowania.]
źródło
const_cast
(które mogłyby zostać przypadkowo użyte do zbrojenia czegoś, co tak naprawdę powinno być const do czegoś, co nie jest).Co powiesz na przeniesienie logiki do metody prywatnej i wykonywanie tylko czynności „pobierz referencję i zwróć” wewnątrz modułów pobierających? Właściwie byłbym dość zdezorientowany co do rzutów statycznych i stałych w prostej funkcji gettera i uważałbym to za brzydkie, z wyjątkiem wyjątkowo rzadkich okoliczności!
źródło
Czy używanie preprocesora jest oszustwem?
Nie jest tak fantazyjny jak szablony lub rzutowania, ale sprawia, że twoja intencja („te dwie funkcje mają być identyczne”) jest dość wyraźna.
źródło
Zaskakuje mnie, że istnieje tak wiele różnych odpowiedzi, ale prawie wszystkie polegają na dużej magii szablonów. Szablony są potężne, ale czasami makra pobijają je w zwięzłości. Maksymalna wszechstronność jest często osiągana przez połączenie obu.
Napisałem makro,
FROM_CONST_OVERLOAD()
które można umieścić w funkcji non-const, aby wywołać funkcję const.Przykładowe użycie:
Prosta implementacja wielokrotnego użytku:
Wyjaśnienie:
Jak napisano w wielu odpowiedziach, typowy wzorzec unikania duplikacji kodu w funkcji niezrzeszonej jest następujący:
Wiele z tej płyty kotłowej można uniknąć, korzystając z wnioskowania o typie. Po pierwsze,
const_cast
może być enkapsulowanyWithoutConst()
, co określa typ jego argumentu i usuwa kwalifikator const. Po drugie, podobne podejście można zastosowaćWithConst()
do stałej kwalifikacjithis
wskaźnika, co umożliwia wywołanie metody przeciążenia const.Reszta to proste makro, które poprzedza połączenie poprawnie zakwalifikowaną
this->
i usuwa const z wyniku. Ponieważ wyrażenie używane w makrze jest prawie zawsze prostym wywołaniem funkcji z argumentami przekazanymi w stosunku 1: 1, nie pojawiają się wady makr, takich jak wielokrotna ocena. Elipsa i__VA_ARGS__
może być również używana, ale nie powinna być potrzebna, ponieważ przecinki (ponieważ separatory argumentów) występują w nawiasach.Takie podejście ma kilka zalet:
FROM_CONST_OVERLOAD( )
const_iterator
,std::shared_ptr<const T>
itp). W tym celu wystarczy przeciążenieWithoutConst()
odpowiednich typów.Ograniczenia: to rozwiązanie jest zoptymalizowane do scenariuszy, w których przeciążenie ciągłe działa dokładnie tak samo jak przeciążenie ciągłe, dzięki czemu argumenty można przekazywać 1: 1. Jeśli Twoja logika jest
this->Method(args)
inna i nie wywołujesz stałej wersji przez , możesz rozważyć inne podejścia.źródło
Dla tych (jak ja), którzy
oto kolejne ujęcie:
Jest to w zasadzie połączenie odpowiedzi z @Pait, @DavidStone i @ sh1 ( EDYCJA : i ulepszenie z @cdhowie). To, co dodaje do tabeli, polega na tym, że unikasz tylko jednego dodatkowego wiersza kodu, który po prostu nazywa nazwę funkcji (ale nie ma powielania argumentów ani typów zwracanych):
Uwaga: gcc nie kompiluje tego przed wersją 8.1, clang-5 i nowsze wersje, a także MSVC-19 są zadowoleni (według eksploratora kompilatora ).
źródło
decltype()
również używaćstd::forward
argumentów, aby upewnić się, że używamy odpowiedniego typu zwrotu w przypadku, gdy mamy przeciążenia,get()
które przyjmują różne typy referencji?NON_CONST
makro rozpoznaje typ zwracany niepoprawnie iconst_cast
S do niewłaściwego typu ze względu na brak spedycji wdecltype(func(a...))
typów. Zastąpienie ichdecltype(func(std::forward<T>(a)...))
rozwiązuje to . (Jest tylko błąd linkera, ponieważ nigdy nie zdefiniowałem żadnego z zadeklarowanychX::get
przeciążeń.)Oto wersja C ++ 17 szablonu statycznej funkcji pomocniczej z opcjonalnym testem SFINAE.
Pełna wersja: https://godbolt.org/z/mMK4r3
źródło
Wymyśliłem makro, które automatycznie generuje pary funkcji const / non-const.
Zobacz koniec odpowiedzi na wdrożenie.
Argument argumentu
MAYBE_CONST
jest zduplikowany. W pierwszym egzemplarzuCV
zostaje zastąpiony niczym; aw drugiej kopii zastąpiono goconst
.Nie ma ograniczenia, ile razy
CV
może występować w argumencie makra.Jest jednak niewielka niedogodność. Jeśli
CV
pojawia się w nawiasach, ta para nawiasów musi być poprzedzonaCV_IN
:Realizacja:
Implementacja wcześniejsza niż C ++ 20, która nie obsługuje
CV_IN
:źródło
Zazwyczaj funkcje składowe, dla których potrzebujesz wersji const i non-const, to gettery i setters. Przeważnie są one jednowarstwowe, więc duplikacja kodu nie stanowi problemu.
źródło
Zrobiłem to dla przyjaciela, który słusznie uzasadnił użycie
const_cast
... nie wiedząc o tym, prawdopodobnie zrobiłbym coś takiego (niezbyt elegancko):źródło
Sugerowałbym szablon funkcji statycznej pomocnika prywatnego, taki jak ten:
źródło
W tym artykule DDJ pokazano sposób korzystania ze specjalizacji szablonów, która nie wymaga użycia const_cast. Jednak dla tak prostej funkcji tak naprawdę nie jest ona potrzebna.
boost :: any_cast (w pewnym momencie już nie używa) używa const_cast z wersji const wywołującej wersję non-const, aby uniknąć powielania. Nie możesz jednak nałożyć stałej semantyki na wersję inną niż const, więc musisz być bardzo ostrożny.
W końcu pewne powielanie kodu jest w porządku, o ile dwa fragmenty znajdują się bezpośrednio nad sobą.
źródło
Aby dodać do dostarczonego rozwiązania jwfearn i kevin, oto odpowiednie rozwiązanie, gdy funkcja zwróci shared_ptr:
źródło
Nie znalazłem tego, czego szukałem, więc wyrzuciłem kilka własnych ...
Ten jest trochę niewygodny, ale ma tę zaletę, że obsługuje wiele przeciążonych metod o tej samej nazwie (i typie zwracanym) jednocześnie:
Jeśli masz tylko jedną
const
metodę na nazwę, ale nadal istnieje wiele metod do powielenia, możesz wybrać:Niestety to się psuje, gdy tylko zaczniesz przeciążać nazwę (lista argumentów argumentu wskaźnika funkcji wydaje się w tym momencie nierozwiązana, więc nie może znaleźć dopasowania dla argumentu funkcji). Chociaż możesz również rozwiązać ten problem:
Ale argumenty odwołujące się do
const
metody nie pasują do argumentów najwyraźniej według wartości do szablonu i psuje się.Nie pewny dlaczego.Oto dlaczego .źródło