Aby uniknąć nietrywialnego powielania związanego z C ++ const, czy istnieją przypadki, w których const_cast działałoby, ale prywatna funkcja const zwracająca non-const nie?
W Effective C ++ pozycja 3 Scott Meyers sugeruje, że const_cast w połączeniu z rzutowaniem statycznym może być skutecznym i bezpiecznym sposobem na uniknięcie powielania kodu, np.
const void* Bar::bar(int i) const
{
...
return variableResultingFromNonTrivialDotDotDotCode;
}
void* Bar::bar(int i)
{
return const_cast<void*>(static_cast<const Bar*>(this)->bar(i));
}
Meyers wyjaśnia dalej, że wywołanie funkcji const przez funkcję non-const jest niebezpieczne.
Poniższy kod jest przykładem pokazującym:
- wbrew sugestii Meyersa, czasami const_cast w połączeniu z obsadą statyczną jest niebezpieczny
- czasami posiadanie wywołania funkcji const, non-const, jest mniej niebezpieczne
- czasami w obu przypadkach użycie const_cast ukrywa potencjalnie przydatne błędy kompilatora
- unikanie const_cast i posiadanie dodatkowego const prywatnego członka zwracającego non-const to kolejna opcja
Czy któraś ze strategii const_cast unikania powielania kodu jest uważana za dobrą praktykę? Wolałbyś zamiast tego strategię prywatnej metody? Czy istnieją przypadki, w których const_cast działałoby, ale nie działała metoda prywatna? Czy istnieją inne opcje (oprócz powielania)?
Moje obawy dotyczące strategii const_cast są takie, że nawet jeśli kod jest poprawny podczas pisania, później podczas konserwacji kod może stać się niepoprawny, a const_cast ukryje przydatny błąd kompilatora. Wygląda na to, że wspólna funkcja prywatna jest ogólnie bezpieczniejsza.
class Foo
{
public:
Foo(const LongLived& constLongLived, LongLived& mutableLongLived)
: mConstLongLived(constLongLived), mMutableLongLived(mutableLongLived)
{}
// case A: we shouldn't ever be allowed to return a non-const reference to something we only have a const reference to
// const_cast prevents a useful compiler error
const LongLived& GetA1() const { return mConstLongLived; }
LongLived& GetA1()
{
return const_cast<LongLived&>( static_cast<const Foo*>(this)->GetA1() );
}
/* gives useful compiler error
LongLived& GetA2()
{
return mConstLongLived; // error: invalid initialization of reference of type 'LongLived&' from expression of type 'const LongLived'
}
const LongLived& GetA2() const { return const_cast<Foo*>(this)->GetA2(); }
*/
// case B: imagine we are using the convention that const means thread-safe, and we would prefer to re-calculate than lock the cache, then GetB0 might be correct:
int GetB0(int i) { return mCache.Nth(i); }
int GetB0(int i) const { return Fibonachi().Nth(i); }
/* gives useful compiler error
int GetB1(int i) const { return mCache.Nth(i); } // error: passing 'const Fibonachi' as 'this' argument of 'int Fibonachi::Nth(int)' discards qualifiers
int GetB1(int i)
{
return static_cast<const Foo*>(this)->GetB1(i);
}*/
// const_cast prevents a useful compiler error
int GetB2(int i) { return mCache.Nth(i); }
int GetB2(int i) const { return const_cast<Foo*>(this)->GetB2(i); }
// case C: calling a private const member that returns non-const seems like generally the way to go
LongLived& GetC1() { return GetC1Private(); }
const LongLived& GetC1() const { return GetC1Private(); }
private:
LongLived& GetC1Private() const { /* pretend a bunch of lines of code instead of just returning a single variable*/ return mMutableLongLived; }
const LongLived& mConstLongLived;
LongLived& mMutableLongLived;
Fibonachi mCache;
};
class Fibonachi
{
public:
Fibonachi()
{
mCache.push_back(0);
mCache.push_back(1);
}
int Nth(int n)
{
for (int i=mCache.size(); i <= n; ++i)
{
mCache.push_back(mCache[i-1] + mCache[i-2]);
}
return mCache[n];
}
int Nth(int n) const
{
return n < mCache.size() ? mCache[n] : -1;
}
private:
std::vector<int> mCache;
};
class LongLived {};
Odpowiedzi:
W przypadku implementacji stałych i niepowiązanych funkcji składowych, które różnią się tylko tym, czy zwracany ptr / referencja jest const, najlepszą strategią DRY jest:
na przykład
Nazwijmy to prywatną funkcją const zwracającą wzorzec non-const .
Jest to najlepsza strategia unikania powielania w prosty sposób, a jednocześnie pozwala kompilatorowi przeprowadzać potencjalnie przydatne kontrole i zgłaszać komunikaty o błędach związanych z const.
źródło
const
instancji (chyba że odniesienie dotyczy czegoś zadeklarowanegomutable
lub chyba, że zastosujesz,const_cast
ale w obu przypadkach nie ma na początku problemu ). Nie mogłem też znaleźć niczego na temat „prywatnej funkcji const zwracającej wzorzec non-const” (jeśli miał być żartem, aby nazwać to wzorem… to nie jest śmieszne;)Tak, masz rację: wiele programów w C ++, które próbują const-poprawność, rażąco narusza zasadę DRY, a nawet prywatny członek powracający non-const jest nieco zbyt skomplikowany dla wygody.
Pomija się jednak jedno spostrzeżenie: powielanie kodu z powodu stałej poprawności zawsze stanowi problem tylko wtedy, gdy udostępniasz inny kod członkom danych. To samo w sobie stanowi naruszenie enkapsulacji. Zasadniczo ten rodzaj duplikacji kodu występuje głównie w prostych akcesoriach (w końcu przekazujesz dostęp do już istniejących elementów, wartość zwracana zasadniczo nie jest wynikiem obliczeń).
Z mojego doświadczenia wynika, że dobre abstrakty nie zawierają zwykle akcesoriów. W związku z tym w dużej mierze unikam tego problemu, definiując funkcje składowe, które faktycznie coś robią, a nie tylko zapewniając dostęp do danych; Staram się modelować zachowanie zamiast danych. Moim głównym celem w tym przypadku jest uzyskanie pewnej abstrakcji zarówno z moich klas, jak i ich poszczególnych funkcji składowych, zamiast po prostu używania moich obiektów jako kontenerów danych. Ale ten styl z powodzeniem pozwala również uniknąć mnóstwa powtarzających się jednowierszowych akcesoriów const / non-const, które są tak powszechne w większości kodów.
źródło