Sabit C ++ KURU Stratejiler


14

Önemsiz C ++ const ile ilgili yinelemeden kaçınmak için, const_cast'in çalışacağı ancak const olmayan dönen özel bir const işlevinin çalışmadığı durumlar var mı?

Scott Meyers'ın Etkili C ++ öğesi 3'te, statik bir dökümle birleştirilen bir const_cast'in yinelenen kodu önlemek için etkili ve güvenli bir yol olabileceğini, ör.

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, const işlevinin const olmayan işlevi çağırmasının tehlikeli olduğunu açıklar.

Aşağıdaki kod aşağıdakileri gösteren bir karşı örnektir:

  • Meyers'in önerisinin aksine, bazen statik bir dökümle birleştirilen const_cast tehlikeli olabilir
  • bazen const işlevinin const olmayanı çağırması daha az tehlikelidir
  • Bazen bir const_cast kullanmanın her iki yolu da potansiyel olarak yararlı derleyici hatalarını gizler
  • const_cast'ten kaçınmak ve const olmayan bir ürünü döndüren ek const özel üyesine sahip olmak başka bir seçenektir

Const_cast stratejilerinden herhangi birinin kod yinelemesinden kaçınmanın iyi bir uygulama olduğu düşünülüyor mu? Bunun yerine özel yöntem stratejisini tercih eder misiniz? Const_cast'in işe yarayacağı ancak özel bir yöntemin işe yaramayacağı durumlar var mı? Başka seçenekler var mı (kopyalamanın yanı sıra)?

Const_cast stratejileriyle ilgili endişem, kod yazıldığında doğru olsa bile, daha sonra bakım sırasında kodun yanlış olabileceği ve const_cast'in yararlı bir derleyici hatasını gizleyeceğidir. Ortak bir özel işlev genellikle daha güvenli gibi görünüyor.

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

Bir üyeyi döndüren alıcı, kendisinin diğer sürümünü yayınlayan ve çağıran kişiden daha kısadır. Hile, veri tekilleştirme kazancının döküm risklerinden ağır bastığı daha karmaşık işlevler içindir.
Sebastian Redl

@SebastianRedl Ben sadece üyeyi iade eğer çoğaltma iyi olacağını kabul ediyorum. Lütfen daha karmaşık olduğunu düşünün, örneğin mConstLongLived öğesini döndürmek yerine, mConstLongLived üzerinde, sahip olmadığımız ve yalnızca erişebileceğimiz bir const referansı döndüren başka bir işlevi çağırmak için kullanılan bir const referansı döndüren bir işlevi çağırabiliriz. sabit sürümü. Umarım nokta const_cast aksi const olmayan erişimi olmayan bir şey const kaldırabilirsiniz açıktır.
JDiMatteo

4
Bu basit örneklerle biraz saçma görünüyor, ancak const ile ilgili çoğaltma gerçek kodda geliyor, const derleyici hataları pratikte (genellikle aptal hataları yakalamak için) yararlıdır ve önerilen "etkili C ++" çözümünün garip olduğunu şaşırıyorum ve görünüşe göre hataya meyilli bir çift oyuncu. Bir const olmayan dönen özel bir const üyesi açıkça bir çift döküm üstün görünüyor ve eksik bir şey olup olmadığını bilmek istiyorum.
JDiMatteo

Yanıtlar:


8

Yalnızca döndürülen ptr / başvurusunun sabit olup olmamasına göre farklılık gösteren sabit ve sabit olmayan üye işlevlerini uygularken, en iyi KURU stratejisi:

  1. Bir erişimci yazıyorsanız, gerçekten erişimciye ihtiyacınız olup olmadığını düşünün, cmaster'ın cevabına bakın ve http://c2.com/cgi/wiki?AccessorsAreEvil
  2. önemsizse kodu çoğaltmanız yeterlidir (ör. yalnızca üyeyi iade etmek)
  3. const ile ilgili çoğaltmayı önlemek için asla const_cast kullanmayın
  4. önemsiz yinelemeyi önlemek için, hem const hem de const olmayan genel işlevlerin çağırdığı bir const olmayan döndüren özel const işlevi kullanın

Örneğin

public:
  LongLived& GetC1() { return GetC1Private(); }
  const LongLived& GetC1() const { return GetC1Private(); }
private:
  LongLived& GetC1Private() const { /* non-trivial DRY logic here */ }

Buna const olmayan desen döndüren özel const işlevi diyelim .

Bu, derleyicinin potansiyel olarak yararlı denetimler gerçekleştirmesine ve sabit hata mesajlarını rapor etmesine izin verirken, kopyaları düz bir şekilde önlemek için en iyi stratejidir.


argümanlarınız oldukça ikna edicidir, ancak bir constörnekten bir şey için const olmayan bir referansı nasıl alabileceğinizi şaşkınım (referans beyan edilen bir şeye mutableveya sürece bir const_castişe almadığınız sürece, ancak her iki durumda da başlamak için bir sorun yoktur. ). Ayrıca "const olmayan desen dönen özel const işlevi" bir şey bulamadı (desen olarak adlandırmak için bir şaka olması amaçlanmışsa .... komik değil;)
idclev 463035818

1
İşte sorudaki koda dayalı derleme örneği: ideone.com/wBE1GB . Üzgünüm, bir şaka olarak kastetmedim, ama burada bir isim vermek istemiştim (olası bir olayda bir ismi hak ediyor) ve cevabı daha açık hale getirmek için ifadeyi güncelledim. Bunu yazmamdan bu yana birkaç yıl geçti ve neden yapıcıdan referans geçen bir örneğin ilgili olduğunu düşündüğümü hatırlamıyorum.
JDiMatteo

Örnek için teşekkürler, şimdi zamanım yok, ama kesinlikle ona geri döneceğim. Fwiw burada aynı yaklaşımı sunan bir cevap ve yorumlarda benzer konular işaret edildi: stackoverflow.com/a/124209/4117728
idclev 463035818

1

Evet, haklısınız: Const-doğruluk girişiminde bulunan birçok C ++ programı, DRY ilkesini açıkça ihlal ediyor ve const olmayan geri dönen özel üye bile rahatlık için biraz fazla karmaşık.

Ancak, bir gözlemi kaçırırsınız: veri doğruluğuna bağlı olarak kod çoğaltma yalnızca veri üyelerinize başka bir kod erişimi veriyorsanız sorun yaratır. Bu kendi içinde kapsülleme ihlalidir. Genellikle, bu tür kod çoğaltma çoğunlukla basit erişimcilerde gerçekleşir (sonuçta, zaten var olan üyelere erişim veriyorsunuz, dönüş değeri genellikle bir hesaplamanın sonucu değildir).

Deneyimlerim, iyi soyutlamaların erişimcileri içermeme eğiliminde olduğudur. Sonuç olarak, sadece veri üyelerine erişim sağlamak yerine, aslında bir şeyler yapan üye işlevlerini tanımlayarak bu sorundan büyük ölçüde kaçınırım; Veri yerine davranışı modellemeye çalışıyorum. Buradaki asıl amacım, nesnelerimi sadece veri kapları olarak kullanmak yerine, hem sınıflarımdan hem de bireysel üye işlevlerinden biraz soyutlama elde etmektir. Ancak bu stil, çoğu kodda çok yaygın olan tonlarca const / const olmayan tekrarlayan tek hatlı erişimciden kaçınmada oldukça başarılıdır.


Erişimcilerin iyi olup olmadığı tartışmalıdır, örneğin c2.com/cgi/wiki?AccessorsAreEvil adresindeki tartışmaya bakın . Uygulamada, erişimciler hakkında ne düşündüğünüzden bağımsız olarak, büyük kod tabanları genellikle bunları kullanır ve bunları kullanırlarsa, KURU ilkesine uymak daha iyi olacaktır. Bence bu soru sormamanız gerekenden daha fazla bir cevabı hak ediyor.
JDiMatteo

1
Kesinlikle sormaya değer bir soru :-) Ve zaman zaman erişimcilere ihtiyacınız olduğunu bile inkar etmeyeceğim. Sadece erişimcilere dayalı olmayan bir programlama tarzının sorunu büyük ölçüde azalttığını söylüyorum. Sorunu tamamen çözmez, ama en azından benim için yeterince iyi.
cmaster - reinstate monica
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.