Benzer sabit ve sabit olmayan üye işlevleri arasındaki kod çoğaltmasını nasıl kaldırabilirim?


242

Diyelim ki class Xbir iç üyeye erişim vermek istediğim yerde aşağıdakiler var :

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

İki üye çalışır X::Z()ve X::Z() constparantez içinde aynı koda sahiptir. Bu yinelenen koddur ve karmaşık mantıkla uzun işlevler için bakım sorunlarına neden olabilir .

Bu kod yinelemesini önlemenin bir yolu var mı?


Bu örnekte const durumunda bir değer döndürürdüm, böylece aşağıdaki yeniden düzenleme yapamazsınız. int Z () const {dönüş z; }
Matt Fiyat

1
Temel türler için kesinlikle haklısınız! İlk örneğim çok iyi değildi. Diyelim ki bunun yerine bir sınıf örneği döndürüyoruz. (Bunu yansıtmak için soruyu güncelledim.)
Kevin

Yanıtlar:


189

Ayrıntılı bir açıklama için, lütfen bkz. "Kopyalama constve constÜye Olmayan İşlevde Yinelenmekten Kaçının" başlığına bakın . 23, Madde 3'te " constMümkün olduğunda kullanın ", Etkili C ++ , 3d ed Scott Meyers, ISBN-13: 9780321334879.

alternatif metin

İşte Meyers'ın çözümü (basitleştirilmiş):

struct C {
  const char & get() const {
    return c;
  }
  char & get() {
    return const_cast<char &>(static_cast<const C &>(*this).get());
  }
  char c;
};

İki yayın ve işlev çağrısı çirkin olabilir, ancak doğru. Meyers'ın neden ayrıntılı bir açıklaması var.


45
Kimse Scott Meyers'ı takip ettiği için kovulmadı :-)
Steve Jessop

11
witkamp genel olarak const_cast kullanmanın kötü olduğu konusunda doğrudur. Bu, Meyers'in açıkladığı gibi, olmadığı bir durumda. @Adam: ROM => const gayet iyi. const == ROM açıkça saçma çünkü herkes willy-nilly const için const olmayan dökmek olabilir: sadece bir şey değiştirmemeyi seçmek için eşdeğerdir.
Steve Jessop

44
Genel olarak, yanlışlıkla tür değiştirmenizi engellediğinden const eklemek için static_cast yerine const_cast kullanmanızı öneririm.
Greg Rogers

6
@HelloGoodbye: Bence Meyers , sınıf arayüzünün tasarımcısından bir miktar zeka alıyor. Eğer get()constbir const nesnesi olarak tanımlandı döner bir şey, o zaman bir const olmayan versiyonu olmamalıdır get()hiç. Aslında bu konudaki düşüncem zaman içinde değişti: şablon çözümü çoğaltmayı önlemenin ve derleyici tarafından kontrol edilen sabit doğruluk elde etmenin tek yoludur , bu yüzden kişisel olarak artık const_castkod çoğaltmaktan kaçınmak için bir çoğaltılan kodu bir işlev şablonuna veya kopyalanmış olarak bırakmaya.
Steve Jessop

7
Aşağıdaki iki şablon, bu çözümün okunabilirliğine çok yardımcı olur: template<typename T> const T& constant(T& _) { return const_cast<const T&>(_); }ve template<typename T> T& variable(const T& _) { return const_cast<T&>(_); }. Sonra şunları yapabilirsiniz:return variable(constant(*this).get());
Casey Rodarmor

64

Evet, kod yinelemesinden kaçınmak mümkündür. Mantık ve sabit olmayan üye işlevinin const üye işlevini çağırması ve dönüş değerini const olmayan bir referansa (veya işlevler bir işaretçi döndürürse işaretçiye) döndürmek için const üye işlevini kullanmanız gerekir:

class X
{
   std::vector<Z> vecZ;

public:
   const Z& z(size_t index) const
   {
      // same really-really-really long access 
      // and checking code as in OP
      // ...
      return vecZ[index];
   }

   Z& z(size_t index)
   {
      // One line. One ugly, ugly line - but just one line!
      return const_cast<Z&>( static_cast<const X&>(*this).z(index) );
   }

 #if 0 // A slightly less-ugly version
   Z& Z(size_t index)
   {
      // Two lines -- one cast. This is slightly less ugly but takes an extra line.
      const X& constMe = *this;
      return const_cast<Z&>( constMe.z(index) );
   }
 #endif
};

NOT: Bu do önemlidir DEĞİL const olmayan işlevinde mantığı koymak ve const fonksiyonlu görüşme const olmayan işleve sahip - bu tanımsız davranışlara neden olabilir. Bunun nedeni, sabit bir sınıf örneğinin sabit olmayan bir örnek olarak atanmasıdır. Sabit olmayan üye işlevi, C ++ standart durumlarının tanımsız davranışa neden olacağı sınıfı yanlışlıkla değiştirebilir.


3
Vay be ... bu korkunç. Sadece kod miktarını artırdınız , netliği azalttınız ve iki adet stinkin 'const_cast <> s eklediniz. Belki de bunun gerçekten mantıklı olduğu bir örnek var mı?
Shog9

14
Hey bunu yapma !, çirkin olabilir, ama Scott Meyers'a göre, (neredeyse) doğru yol. Bkz Etkili C ++ , 3d ed, const olmayan maliyet üyesi işlevlerinde tekrarını önleme başlığı" altında Öğe 3.
jwfearn

17
Çözümün çirkin olabileceğini anlasam da, ne döndüreceğini belirleyen kodun 50 satır uzunluğunda olduğunu hayal edin. Daha sonra çoğaltma istenmeyen bir durumdur - özellikle kodu yeniden faktörlendirmeniz gerektiğinde. Bunu kariyerimde birçok kez gördüm.
Kevin

8
Bu ve Meyers arasındaki fark, Meyers'in static_cast <const X &> (* this) özelliğidir. const_cast, const'u eklemek için değil, onu kaldırmak içindir.
Steve Jessop

8
@VioletGiraffe, söz konusu nesnenin const olmayan bir yönteminde olduğumuz için bildiğimiz, const olmayan bir nesnenin const olmayan bir üyesi olduğu için, nesnenin başlangıçta const oluşturulmadığını biliyoruz. Derleyici bu çıkarımda bulunmaz, muhafazakar bir kural izler. Sizce neden böyle bir durum olmasa da const_cast var?
Caleth

47

C ++ 17, bu soru için en iyi yanıtı güncelledi:

T const & f() const {
    return something_complicated();
}
T & f() {
    return const_cast<T &>(std::as_const(*this).f());
}

Bunun avantajları vardır:

  • Neler olduğu açık mı
  • Minimum kod yükü vardır - tek bir satıra sığar
  • Yanlış anlaşılması zor (sadece volatilekazara atılabilir, ancak volatilenadir bir niteleyicidir)

Tam kesinti yoluna gitmek istiyorsanız, yardımcı fonksiyona sahip olarak gerçekleştirilebilir

template<typename T>
constexpr T & as_mutable(T const & value) noexcept {
    return const_cast<T &>(value);
}
template<typename T>
constexpr T * as_mutable(T const * value) noexcept {
    return const_cast<T *>(value);
}
template<typename T>
constexpr T * as_mutable(T * value) noexcept {
    return value;
}
template<typename T>
void as_mutable(T const &&) = delete;

Artık ortalığı karıştıramazsınız volatileve kullanım

decltype(auto) f() const {
    return something_complicated();
}
decltype(auto) f() {
    return as_mutable(std::as_const(*this).f());
}

Sabit değer aşırı yükü silindiğinde (genellikle tercih edilir) "as_mutable" ifadesinin, bunun yerine f()döndürürse son örneğin çalışmasını engellediğini unutmayın . TT&
Max Truxa

1
@ MaxTruxa: Evet ve bu iyi bir şey. Eğer derlenmiş olsaydı, sarkan bir referansımız olurdu. Durumunda f()döner T, iki aşırı yükleri olmasını istemiyoruz, constyalnız versiyonu yeterlidir.
David Stone

Çok doğru, dün tam beyin osuruğum için özür dilerim, bu yorumu yazarken ne düşündüğümü bilmiyorum. Ben dönen bir const / mutable alıcı çift bakıyordu shared_ptr. Yani benim asıl ihtiyacın varmış gibi bir şeydi as_mutable_ptrneredeyse aynı görünüyor ki as_mutableo alır ve duruma göre bir olması dışında yukarıda shared_ptrve kullanımları std::const_pointer_castyerine const_cast.
Max Truxa

1
Bir yöntem döndürürse, T const*bu bağlanmak T const* const&&yerine bağlanır T const* const&(en azından benim testimde). T const*Bir işaretçi döndürme yöntemleri için bağımsız değişken türü olarak bir aşırı yük eklemek zorunda kaldı .
monkey0506

2
@ monkey0506: Referansları olduğu kadar işaretçileri de destekledim
David Stone

34

Scott Meyers'ın çözümü C ++ 11'de geçici bir yardımcı işlevi kullanılarak geliştirilebileceğini düşünüyorum. Bu, niyeti çok daha açık hale getirir ve diğer birçok alıcı için tekrar kullanılabilir.

template <typename T>
struct NonConst {typedef T type;};
template <typename T>
struct NonConst<T const> {typedef T type;}; //by value
template <typename T>
struct NonConst<T const&> {typedef T& type;}; //by reference
template <typename T>
struct NonConst<T const*> {typedef T* type;}; //by pointer
template <typename T>
struct NonConst<T const&&> {typedef T&& type;}; //by rvalue-reference

template<typename TConstReturn, class TObj, typename... TArgs>
typename NonConst<TConstReturn>::type likeConstVersion(
   TObj const* obj,
   TConstReturn (TObj::* memFun)(TArgs...) const,
   TArgs&&... args) {
      return const_cast<typename NonConst<TConstReturn>::type>(
         (obj->*memFun)(std::forward<TArgs>(args)...));
}

Bu yardımcı fonksiyon aşağıdaki şekilde kullanılabilir.

struct T {
   int arr[100];

   int const& getElement(size_t i) const{
      return arr[i];
   }

   int& getElement(size_t i) {
      return likeConstVersion(this, &T::getElement, i);
   }
};

İlk argüman her zaman bu imleçtir. İkincisi, çağrılacak üye fonksiyonunun göstergesidir. Bundan sonra, işleve iletilebilmeleri için rastgele miktarda ek argümanlar iletilebilir. Variadic şablonları nedeniyle C ++ 11 gerekir.


3
Biz yok bir utanç std::remove_bottom_constile gitmek std::remove_const.
TBBle

Bu çözümü sevmiyorum çünkü hala a const_cast. getElementBir şablon oluşturabilir ve mpl::conditionalihtiyaç duyduğunuz türlerde, gerekirse iterators veya constiterators gibi içerideki türün özelliğini kullanabilirsiniz . Asıl sorun, imzanın bu kısmı geçici hale getirilemediğinde bir yöntemin const sürümü nasıl oluşturulur?
v.oddou

2
@ v.oddou: std::remove_const<int const&>olduğu int const &(kaldırmak üst düzey constyeterlilik), bu nedenle jimnastik NonConst<T>Bu yanıt olarak. Varsayılan std::remove_bottom_constolarak alt düzey constkalifikasyon kaldırılabilir ve NonConst<T>burada tam olarak ne yapılır : std::remove_bottom_const<int const&>::type=> int&.
TBBle

4
getElementAşırı yüklenmişse bu çözüm iyi çalışmaz . Ardından işlev işaretçisi, şablon parametreleri açıkça verilmeden çözülemez. Neden?
John

1
C ++ 11 mükemmel yönlendirmeyi kullanmak için cevabınızı düzeltmeniz gerekiyor: likeConstVersion(TObj const* obj, TConstReturn (TObj::*memFun)(TArgs...) const, TArgs&&... args) { return const_cast<typename NonConst<TConstReturn>::type>((obj->*memFun)(std::forward<TArgs>(args)...)); }Tamamlandı: gist.github.com/BlueSolei/bca26a8590265492e2f2760d3cefcf83
ShaulF

22

Meyers'den biraz daha ayrıntılı, ama bunu yapabilirim:

class X {

    private:

    // This method MUST NOT be called except from boilerplate accessors.
    Z &_getZ(size_t index) const {
        return something;
    }

    // boilerplate accessors
    public:
    Z &getZ(size_t index)             { return _getZ(index); }
    const Z &getZ(size_t index) const { return _getZ(index); }
};

Özel yöntem, const örneği için & const örneği için istenmeyen bir Z döndürdüğü istenmeyen özelliğe sahiptir, bu yüzden özeldir. Özel yöntemler dış arabirimin değişmezlerini kırabilir (bu durumda istenen değişmez "bir const nesnesi, onun aracılığıyla-a'ya sahip nesneler üzerinden elde edilen referanslar yoluyla değiştirilemez").

Yorumların kalıbın bir parçası olduğunu unutmayın - _getZ'nin arabirimi, onu çağırmanın hiçbir zaman geçerli olmadığını belirtir (açıkça erişimciler dışında): Bunu yapmak için akla yatkın bir fayda yoktur, çünkü yazmak için 1 karakter daha vardır ve daha küçük veya daha hızlı kodla sonuçlanır. Yöntemin çağrılması, const_cast ile erişimcilerden birini çağırmakla eşdeğerdir ve bunu da yapmak istemezsiniz. Hataları açık hale getirmekten endişe ediyorsanız (ve bu adil bir hedefse), _getZ yerine const_cast_getZ olarak adlandırın.

Bu arada, Meyers'ın çözümünü takdir ediyorum. Buna felsefi bir itirazım yok. Şahsen, biraz kontrollü tekrarlamayı ve hat gürültüsüne benzeyen bir yöntem yerine sadece sıkı sıkı kontrol edilen bazı durumlarda çağrılması gereken özel bir yöntemi tercih ediyorum. Zehirini seç ve ona bağlı kal.

[Düzenleme: Kevin haklı olarak _getZ'nin getZ ile aynı şekilde const-specialize edilmiş başka bir metodu çağırmak isteyebileceğine dikkat çekti. Bu durumda, _getZ bir const Z & görür ve geri dönmeden önce const_cast yapar. Isıtıcı plaka erişimcisi her şeyi polise ettiği için hala güvenlidir, ancak güvenli olduğu son derece açık değildir. Ayrıca, bunu yaparsanız ve daha sonra daima const döndürmek için generaZ'i değiştirirseniz, her zaman const döndürmek için getZ'yi değiştirmeniz gerekir, ancak derleyici bunu yaptığınızı söylemez.

Derleyici hakkındaki bu ikinci nokta, Meyers'in önerilen modeli için de geçerlidir, ancak açık olmayan bir const_cast ile ilgili ilk nokta doğru değildir. Denge üzerinde _getZ dönüş değeri için bir const_cast'e ihtiyaç duyarsa, bu örüntünün Meyers'in üzerindeki değerinin çoğunu kaybettiğini düşünüyorum. Meyers'le karşılaştırıldığında dezavantajları olduğu için, bu durumda ona geçeceğimi düşünüyorum. Birinden diğerine yeniden düzenleme yapmak kolaydır - yalnızca geçersiz kod ve ortak plaka _getZ'yi çağırdığından, sınıftaki diğer geçerli kodları etkilemez.]


3
Bu, döndürdüğünüz şeyin X'in sabit bir örneği için sabit olabileceği sorununa sahiptir. Bu durumda, _getZ (...) içinde hala bir const_cast'e ihtiyacınız vardır. Daha sonraki geliştiriciler tarafından yanlış kullanılırsa, yine de UB'ye yol açabilir. İade edilen şey 'değiştirilebilir' ise, bu iyi bir çözümdür.
Kevin

1
Herhangi bir özel işlev (heck, herkese açık olanlar) daha sonraki geliştiriciler tarafından yanlış kullanılabilir, eğer BLOCK CAPITAL talimatlarını geçerli kullanımında, başlık dosyasında ve Doxygen vb. ve bunu benim sorunum olarak görmüyorum çünkü talimatların anlaşılması kolay.
Steve Jessop

13
-1: Bu pek çok durumda çalışmaz. Ya somethingiçinde _getZ()bir örnek değişkeni fonksiyonu nedir? Derleyici (veya en azından bazı derleyiciler) _getZ()const olduğundan , içinde başvurulan herhangi bir örnek değişkeninin de const olduğundan şikayet edecektir . Böylece somethingconst (tür olurdu const Z&) olurdu ve dönüştürülemedi Z&. (Kuşkusuz biraz sınırlı) deneyimlerime göre, çoğu zaman somethingbu gibi durumlarda bir örnek değişkendir.
Yerçekimi

2
@ GravityBringer: o zaman "bir şey" i içermesi gerekir a const_cast. Çoğaltılmış alıcıda yer alacak bir yer tutucu olarak değil, const nesnesinden const olmayan bir dönüş elde etmek için gereken kod için bir yer tutucu olması amaçlanmıştır . Yani "bir şey" sadece bir örnek değişkeni değildir.
Steve Jessop

2
Anlıyorum. Yine de bu, tekniğin kullanışlılığını gerçekten azaltır. Aşağı oyu kaldırırdım, ama SO izin vermiyor.
Yerçekimi

22

Güzel soru ve güzel cevaplar. Hiçbir döküm kullanarak başka bir çözüm var:

class X {

private:

    std::vector<Z> v;

    template<typename InstanceType>
    static auto get(InstanceType& instance, std::size_t i) -> decltype(instance.get(i)) {
        // massive amounts of code for validating index
        // the instance variable has to be used to access class members
        return instance.v[i];
    }

public:

    const Z& get(std::size_t i) const {
        return get(*this, i);
    }

    Z& get(std::size_t i) {
        return get(*this, i);
    }

};

Bununla birlikte, statik bir üyeye ihtiyaç duyma çirkinliğine ve instance içinde değişken .

Bu çözümün tüm olası (olumsuz) sonuçlarını dikkate almadım. Lütfen varsa bana bildirin.


4
Daha fazla kazan plakası eklediğiniz basit gerçeğiyle gidelim. Herhangi bir şey varsa, bu, dilin neden dönüş türüyle birlikte işlev niteleyicilerini değiştirmek için bir yola ihtiyaç duyduğunun bir örneği olarak kullanılmalıdır auto get(std::size_t i) -> auto(const), auto(&&). Neden '&&'? Ahh, bu yüzden söyleyebilirim:auto foo() -> auto(const), auto(&&) = delete;
kfsone

gd1: tam olarak aklımdakilerdi. @kfsone ve tam olarak ne sonuçlandırdım.
v.oddou

1
@kfsone sözdizimi thisanahtar kelimeyi içermelidir . Önerim template< typename T > auto myfunction(T this, t args) -> decltype(ident)bu kelime örtülü nesne örneği argüman olarak kabul edilecek ve derleyici o myfunction üyesi veya tanıyalım T. Ther zaman sınıfın türü olacak, ancak ücretsiz cv niteliği olan çağrı sitesinde otomatik olarak düşülecektir.
v.oddou

2
Bu çözüm, aynı zamanda (karşı avantajı const_castdönmesine imkan vermek için bir tane) iteratorve const_iterator.
Jarod42

1
Uygulama cpp dosyasında taşındıysa (ve çoğaltılmaması gereken yöntem önemsiz olmamalı, muhtemelen böyle olur), staticsınıf kapsamı yerine dosya kapsamında yapılabilir. :-)
Jarod42

8

Bunu şablonlarla da çözebilirsiniz. Bu çözüm biraz çirkin (ama çirkinlik .cpp dosyasında gizlidir), ancak derleyicinin sabitliğini kontrol etmesini sağlar ve kod çoğaltması yapmaz.

.h dosyası:

#include <vector>

class Z
{
    // details
};

class X
{
    std::vector<Z> vecZ;

public:
    const std::vector<Z>& GetVector() const { return vecZ; }
    std::vector<Z>& GetVector() { return vecZ; }

    Z& GetZ( size_t index );
    const Z& GetZ( size_t index ) const;
};

.cpp dosyası:

#include "constnonconst.h"

template< class ParentPtr, class Child >
Child& GetZImpl( ParentPtr parent, size_t index )
{
    // ... massive amounts of code ...

    // Note you may only use methods of X here that are
    // available in both const and non-const varieties.

    Child& ret = parent->GetVector()[index];

    // ... even more code ...

    return ret;
}

Z& X::GetZ( size_t index )
{
    return GetZImpl< X*, Z >( this, index );
}

const Z& X::GetZ( size_t index ) const
{
    return GetZImpl< const X*, const Z >( this, index );
}

Görebildiğim ana dezavantaj, yöntemin tüm karmaşık uygulaması küresel bir işlevde olduğu için, yukarıda GetVector () gibi genel yöntemleri kullanarak X üyelerini tutmanız gerektiğidir (bunun her zaman bir const ve const olmayan sürüm) veya bu işlevi bir arkadaş haline getirebilirsiniz. Ama arkadaşları sevmiyorum.

[Düzenle: gereksiz testler sırasında eklenen cstdio dahil kaldırıldı.]


3
Özel üyelere erişmek için her zaman karmaşık uygulama işlevini statik bir üye yapabilirsiniz. İşlev yalnızca sınıf üstbilgisi dosyasında bildirilmelidir; tanım, sınıf uygulama dosyasında bulunabilir. Ne de olsa sınıf uygulamasının bir parçasıdır.
CB Bailey

Aah evet iyi fikir! Başlıkta görünen şablon öğelerini sevmiyorum, ancak buradan beri potansiyel olarak uygulamayı çok daha basit hale getirirse muhtemelen buna değer.
Andy Balaam

+1 herhangi bir kod yinelenen ne de herhangi bir çirkin kullanır vermez bu çözüme const_cast(yanlışlıkla olan canst şey için kullanılabilecek olan aslında olmayan bir şeyi const olması gerekiyordu).
HelloGoodbye

Günümüzde bu, şablon için çıkarılan bir dönüş türüyle basitleştirilebilir (özellikle üye durumda sınıfta çoğaltılması gerekenleri azalttığı için yararlıdır).
Davis Herring

3

Mantığı özel bir yönteme taşımaya ve yalnızca alıcıların içindeki "referans al ve geri dön" öğelerini yapmaya ne dersiniz? Aslında, basit bir alıcı işlevi içindeki statik ve sabit dökümler hakkında oldukça kafam karışırdı ve son derece nadir durumlar dışında bunu çirkin olarak düşünürdüm!


Tanımlanmamış davranışlardan kaçınmak için hala bir const_cast'e ihtiyacınız vardır. Martin York'un cevabına ve oradaki yorumuma bakın.
Kevin

1
Kevin, Martin York'un cevabı
Peter Nimmo

2

Önişlemciyi kullanmak hile yapıyor mu?

struct A {

    #define GETTER_CORE_CODE       \
    /* line 1 of getter code */    \
    /* line 2 of getter code */    \
    /* .....etc............. */    \
    /* line n of getter code */       

    // ^ NOTE: line continuation char '\' on all lines but the last

   B& get() {
        GETTER_CORE_CODE
   }

   const B& get() const {
        GETTER_CORE_CODE
   }

   #undef GETTER_CORE_CODE

};

Şablonlar ya da yayınlar kadar süslü değil, ama niyetinizi ("bu iki işlev aynı olacak") oldukça açık yapıyor.


1
Ama sonra ters eğik çizgilere (çok satırlı makrolar için her zamanki gibi) dikkat etmelisiniz ve ek olarak çoğu (hepsi değilse de) editörlerde sözdizimi vurgulamayı kaybedersiniz.
Ruslan

2

Çok fazla farklı cevabın olması beni şaşırtıyor, ancak neredeyse hepsi ağır şablon büyüsüne güveniyor. Şablonlar güçlüdür, ancak bazen makrolar kısaca onları yener. Maksimum çok yönlülük genellikle her ikisini birleştirerek elde edilir.

Bir makro yazdım FROM_CONST_OVERLOAD()Const işlevini çağırmak için const işlevine yerleştirilemeyen .

Örnek kullanım:

class MyClass
{
private:
    std::vector<std::string> data = {"str", "x"};

public:
    // Works for references
    const std::string& GetRef(std::size_t index) const
    {
        return data[index];
    }

    std::string& GetRef(std::size_t index)
    {
        return FROM_CONST_OVERLOAD( GetRef(index) );
    }


    // Works for pointers
    const std::string* GetPtr(std::size_t index) const
    {
        return &data[index];
    }

    std::string* GetPtr(std::size_t index)
    {
        return FROM_CONST_OVERLOAD( GetPtr(index) );
    }
};

Basit ve tekrar kullanılabilir uygulama:

template <typename T>
T& WithoutConst(const T& ref)
{
    return const_cast<T&>(ref);
}

template <typename T>
T* WithoutConst(const T* ptr)
{
    return const_cast<T*>(ptr);
}

template <typename T>
const T* WithConst(T* ptr)
{
    return ptr;
}

#define FROM_CONST_OVERLOAD(FunctionCall) \
  WithoutConst(WithConst(this)->FunctionCall)

Açıklama:

Birçok yanıtta da belirtildiği gibi, sabit olmayan bir üye işlevde kod yinelemesini önlemek için tipik desen şudur:

return const_cast<Result&>( static_cast<const MyClass*>(this)->Method(args) );

Bu kazan plakasının birçoğu tip çıkarımı kullanılarak önlenebilir. İlk olarak, argümanının türünü ihlal eden ve sabit niteleyiciyi kaldıran const_castkapsüllenebilir WithoutConst(). İkincisi, benzer bir yaklaşım imlecin WithConst()const-nitelendirilmesi için kullanılabilir this, bu da const aşırı yüklenmiş yöntemi çağırmayı sağlar.

Geri kalanı, çağrıyı doğru şekilde nitelendiren this->ve sonucun sabitini kaldıran basit bir makrodur . Makroda kullanılan ifade neredeyse her zaman 1: 1 iletilen argümanlarla basit bir işlev çağrısı olduğundan, çoklu değerlendirme gibi makroların dezavantajları devreye girmez. Üç nokta ve__VA_ARGS__ ayrıca kullanılabilir, ancak virgül (çünkü bağımsız değişken ayırıcılar) parantez içinde gerçekleşir.

Bu yaklaşımın çeşitli faydaları vardır:

  • Minimal ve doğal sözdizimi - sadece aramayı sarın FROM_CONST_OVERLOAD( )
  • Ek üye işlevi gerekmez
  • C ++ 98 ile uyumlu
  • Basit uygulama, şablon meta programlaması ve sıfır bağımlılık yok
  • Genişletilebilir: Diğer const ilişkiler (gibi eklenebilir const_iterator, std::shared_ptr<const T>vs.). Bunun WithoutConst()için, karşılık gelen tipler için aşırı yükleme yapın .

Sınırlamalar: bu çözüm sabit olmayan aşırı yüklenmenin sabit aşırı yüklemeyle tamamen aynı olduğu senaryolar için optimize edilmiştir, böylece argümanlar 1: 1 iletilebilir. Mantık farklıysa ve const sürümünü üzerinden çağırmıyorsanız, this->Method(args)diğer yaklaşımları düşünebilirsiniz.


2

Benim gibi olanlar için

  • Kullanım c ++ 17
  • en az miktarda kazan plakası / tekrar eklemek ve
  • makroları kullanmaktan çekinmeyin (meta sınıfları beklerken ...),

İşte başka bir şey:

#include <utility>
#include <type_traits>

template <typename T> struct NonConst;
template <typename T> struct NonConst<T const&> {using type = T&;};
template <typename T> struct NonConst<T const*> {using type = T*;};

#define NON_CONST(func)                                                     \
    template <typename... T> auto func(T&&... a)                            \
        -> typename NonConst<decltype(func(std::forward<T>(a)...))>::type   \
    {                                                                       \
        return const_cast<decltype(func(std::forward<T>(a)...))>(           \
            std::as_const(*this).func(std::forward<T>(a)...));              \
    }

Temel olarak @Pait, @DavidStone ve @ sh1 ( EDIT : ve @cdhowie'den bir gelişme) cevaplarının bir karışımıdır . Tabloya eklediği şey, yalnızca işlevi adlandıran tek bir ek kod satırından kurtulmanızdır (ancak bağımsız değişken veya dönüş türü çoğaltma yok):

class X
{
    const Z& get(size_t index) const { ... }
    NON_CONST(get)
};

Not: gcc bunu 8.1'den önce derleyemezse, clang-5 ve üstü ile MSVC-19 mutludur ( derleyici kaşifine göre ).


Bu benim için düzgün çalıştı. Bu harika bir cevap, teşekkürler!
Kısa

Olmamalı decltype()ler de kullanıyor std::forwardbiz bir aşırı yükler gelmiş durumda doğru dönüş türü kullandığınızdan emin olmak için argümanlar üzerinde get()referansların farklı türde almak?
cdhowie

@cdhowie Bir örnek verebilir misiniz?
axxel

@axxel Cehennem gibi davranıyor, ama işte gidiyorsun . NON_CONSTDönüş türü hatalı ve makro deduces const_castnedeniyle de yönlendirme eksikliğinden yanlış tipine s decltype(func(a...))türleri. Bunları değiştirmek decltype(func(std::forward<T>(a)...)) bunu çözer . (Bildirilen X::getaşırı
yüklerden

1
Teşekkürler @cdhowie, ben aslında const olmayan aşırı yükleri
axxel

1

İşte şablon statik yardımcı fonksiyonunun C ++ 17 sürümü ve isteğe bağlı SFINAE testi.

#include <type_traits>

#define REQUIRES(...)         class = std::enable_if_t<(__VA_ARGS__)>
#define REQUIRES_CV_OF(A,B)   REQUIRES( std::is_same_v< std::remove_cv_t< A >, B > )

class Foobar {
private:
    int something;

    template<class FOOBAR, REQUIRES_CV_OF(FOOBAR, Foobar)>
    static auto& _getSomething(FOOBAR& self, int index) {
        // big, non-trivial chunk of code...
        return self.something;
    }

public:
    auto& getSomething(int index)       { return _getSomething(*this, index); }
    auto& getSomething(int index) const { return _getSomething(*this, index); }
};

Tam sürüm: https://godbolt.org/z/mMK4r3


1

Otomatik olarak const / const olmayan fonksiyon çiftleri üreten bir makro buldum.

class A
{
    int x;    
  public:
    MAYBE_CONST(
        CV int &GetX() CV {return x;}
        CV int &GetY() CV {return y;}
    )

    //   Equivalent to:
    // int &GetX() {return x;}
    // int &GetY() {return y;}
    // const int &GetX() const {return x;}
    // const int &GetY() const {return y;}
};

Uygulama için cevabın sonuna bakınız.

Argümanı MAYBE_CONSTçoğaltılır. İlk kopyada, CVhiçbir şey değiştirilmez; ve ikinci kopyada onun yerini alır const.

CVMakro argümanında kaç kez görünebileceğine dair bir sınırlama yoktur .

Yine de hafif bir rahatsızlık var. Eğer CViçeride parantez göründüğünü, parantez bu çifti öneki olmalıdır CV_IN:

// Doesn't work
MAYBE_CONST( CV int &foo(CV int &); )

// Works, expands to
//         int &foo(      int &);
//   const int &foo(const int &);
MAYBE_CONST( CV int &foo CV_IN(CV int &); )

Uygulama:

#define MAYBE_CONST(...) IMPL_CV_maybe_const( (IMPL_CV_null,__VA_ARGS__)() )
#define CV )(IMPL_CV_identity,
#define CV_IN(...) )(IMPL_CV_p_open,)(IMPL_CV_null,__VA_ARGS__)(IMPL_CV_p_close,)(IMPL_CV_null,

#define IMPL_CV_null(...)
#define IMPL_CV_identity(...) __VA_ARGS__
#define IMPL_CV_p_open(...) (
#define IMPL_CV_p_close(...) )

#define IMPL_CV_maybe_const(seq) IMPL_CV_a seq IMPL_CV_const_a seq

#define IMPL_CV_body(cv, m, ...) m(cv) __VA_ARGS__

#define IMPL_CV_a(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_b)
#define IMPL_CV_b(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_a)

#define IMPL_CV_const_a(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_b)
#define IMPL_CV_const_b(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_a)

Desteklemeyen C ++ 20 öncesi uygulama CV_IN:

#define MAYBE_CONST(...) IMPL_MC( ((__VA_ARGS__)) )
#define CV ))((

#define IMPL_MC(seq) \
    IMPL_MC_end(IMPL_MC_a seq) \
    IMPL_MC_end(IMPL_MC_const_0 seq)

#define IMPL_MC_identity(...) __VA_ARGS__
#define IMPL_MC_end(...) IMPL_MC_end_(__VA_ARGS__)
#define IMPL_MC_end_(...) __VA_ARGS__##_end

#define IMPL_MC_a(elem) IMPL_MC_identity elem IMPL_MC_b
#define IMPL_MC_b(elem) IMPL_MC_identity elem IMPL_MC_a
#define IMPL_MC_a_end
#define IMPL_MC_b_end

#define IMPL_MC_const_0(elem)       IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a(elem) const IMPL_MC_identity elem IMPL_MC_const_b
#define IMPL_MC_const_b(elem) const IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a_end
#define IMPL_MC_const_b_end

0

Genellikle, const ve const olmayan sürümlere ihtiyaç duyduğunuz üye işlevleri alıcılar ve ayarlayıcılardır. Çoğu zaman tek satırlıdırlar, bu nedenle kod çoğaltma bir sorun değildir.


2
Bu çoğu zaman doğru olabilir. Ancak istisnalar var.
Kevin

1
Yine de getters, bir const setter pek mantıklı değil;)
jwfearn

Yani sabit olmayan alıcı etkili bir pasör. :)
Dima

0

Bunu haklı olarak kullanımını haklı çıkaran bir arkadaşım için yaptım const_cast... bilmeden muhtemelen böyle bir şey yapardım (gerçekten zarif değil):

#include <iostream>

class MyClass
{

public:

    int getI()
    {
        std::cout << "non-const getter" << std::endl;
        return privateGetI<MyClass, int>(*this);
    }

    const int getI() const
    {
        std::cout << "const getter" << std::endl;
        return privateGetI<const MyClass, const int>(*this);
    }

private:

    template <class C, typename T>
    static T privateGetI(C c)
    {
        //do my stuff
        return c._i;
    }

    int _i;
};

int main()
{
    const MyClass myConstClass = MyClass();
    myConstClass.getI();

    MyClass myNonConstClass;
    myNonConstClass.getI();

    return 0;
}

0

Bunun gibi özel bir yardımcı statik işlev şablonu önerirsiniz:

class X
{
    std::vector<Z> vecZ;

    // ReturnType is explicitly 'Z&' or 'const Z&'
    // ThisType is deduced to be 'X' or 'const X'
    template <typename ReturnType, typename ThisType>
    static ReturnType Z_impl(ThisType& self, size_t index)
    {
        // massive amounts of code for validating index
        ReturnType ret = self.vecZ[index];
        // even more code for determining, blah, blah...
        return ret;
    }

public:
    Z& Z(size_t index)
    {
        return Z_impl<Z&>(*this, index);
    }
    const Z& Z(size_t index) const
    {
        return Z_impl<const Z&>(*this, index);
    }
};

-1

Bu DDJ makalesi , const_cast kullanmanızı gerektirmeyen şablon uzmanlığını kullanmanın bir yolunu gösterir. Böyle basit bir işlev için gerçekten gerekli değildir.

boost :: any_cast (bir noktada, artık yok) const sürümünden çoğaltmayı önlemek için const olmayan sürümü çağıran bir const_cast kullanır. Const olmayan sürümde const semantiği dayatamazsınız, bu yüzden çok dikkat etmelisiniz.

Sonunda bazı kod çoğaltma olduğu sürece iki parçacıkları birbiri üzerine doğrudan olarak tamam.


DDJ makalesi, soruyla ilgili olmayan yineleyicilere atıfta bulunuyor gibi görünüyor. Sabit yineleyiciler sabit veri değildir - sabit verilere işaret eden yineleyicilerdir.
Kevin

-1

Sağlanan jwfearn ve kevin çözümüne eklemek için işlev shared_ptr döndürdüğünde karşılık gelen çözüm:

struct C {
  shared_ptr<const char> get() const {
    return c;
  }
  shared_ptr<char> get() {
    return const_pointer_cast<char>(static_cast<const C &>(*this).get());
  }
  shared_ptr<char> c;
};

-1

Aradığımı bulamadım, bu yüzden birkaç tane yuvarladım ...

Bu biraz garip, ancak aynı adda (ve dönüş tipinde) birçok aşırı yüklenmiş yöntemi bir kerede işleme avantajına sahiptir:

struct C {
  int x[10];

  int const* getp() const { return x; }
  int const* getp(int i) const { return &x[i]; }
  int const* getp(int* p) const { return &x[*p]; }

  int const& getr() const { return x[0]; }
  int const& getr(int i) const { return x[i]; }
  int const& getr(int* p) const { return x[*p]; }

  template<typename... Ts>
  auto* getp(Ts... args) {
    auto const* p = this;
    return const_cast<int*>(p->getp(args...));
  }

  template<typename... Ts>
  auto& getr(Ts... args) {
    auto const* p = this;
    return const_cast<int&>(p->getr(args...));
  }
};

constAd başına yalnızca bir yönteminiz varsa , ancak çoğaltmak için hala çok sayıda yönteminiz varsa, bunu tercih edebilirsiniz:

  template<typename T, typename... Ts>
  auto* pwrap(T const* (C::*f)(Ts...) const, Ts... args) {
    return const_cast<T*>((this->*f)(args...));
  }

  int* getp_i(int i) { return pwrap(&C::getp_i, i); }
  int* getp_p(int* p) { return pwrap(&C::getp_p, p); }

Ne yazık ki bu, adı aşırı yüklemeye başlar başlamaz bozuluyor (işlev işaretçisi argümanının bağımsız değişken listesi o noktada çözülmemiş gibi görünüyor, bu nedenle işlev bağımsız değişkeni için bir eşleşme bulamıyor). Bundan kurtulmanın yolunu da oluşturabilirsin:

  template<typename... Ts>
  auto* getp(Ts... args) { return pwrap<int, Ts...>(&C::getp, args...); }

Ancak constyönteme başvuru bağımsız değişkenleri, şablonun görünüşe göre by-değer bağımsız değişkenleriyle eşleşemez ve kesilir. Emin değilim neden. İşte nedeni .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.