Soru Operatör aşırı yüklenmesi için temel kurallar ve deyimler nelerdir?


Not: cevaplar verildi belirli bir siparişAncak birçok kullanıcı cevapları verilen zamandan ziyade oylara göre sıraladığından cevapların indeksi en mantıklı oldukları sırayla:

(Not: Bunun bir giriş olması amaçlanmıştır. Stack Overflow'un C ++ SSS'si. Bu formda bir SSS sağlama fikrini eleştirmek isterseniz tüm bunları başlatan metada gönderme Bunu yapmak için yer olurdu. Bu soruya verilen cevaplar C ++ sohbet odasıSSS sorununun ilk etapta başladığı yerde, cevabınız fikrini ortaya çıkaranların okumasını çok olasıdır.)  


1845
2017-12-12 12:44


Menşei


C ++ - SSS etiketiyle devam edeceksek, girişlerin nasıl biçimlendirilmesi gerektiği budur. - John Dibling
Alman C ++ topluluğu için operatör aşırı yüklemesiyle ilgili kısa bir yazı dizisi yazdım: Bölüm 1: C ++ 'da aşırı yüklenen operatör tüm operatörler için semantik, tipik kullanım ve spesiyaliteleri kapsar. Burada cevaplarınızda bazı örtüler var, yine de bazı ek bilgiler var. 2. ve 3. bölümler, Boost.Operators'ı kullanmak için bir eğitici yapmaktadır. Onları çevirip yanıt olarak eklememi ister misiniz? - Arne Mertz
Oh, ve bir İngilizce çeviri de mevcuttur: temeller ve yaygın uygulama - Arne Mertz


Cevaplar:


Ortak operatörler aşırı yüklenmeye

Aşırı yükleme operatörlerinde yapılan çalışmaların çoğu kazan-plaka kodudur. Bu biraz şahane, çünkü operatörler sadece sözdizimsel şeker olduklarından, gerçek işleri düz işlevlerle (ve çoğunlukla) iletilebilirdi. Ancak bu kazan-plaka kodunu doğru almanız önemlidir. Başarısız olursanız, operatörünüzün kodu ya derlenmez ya da kullanıcılarınızın kodu derlenmez ya da kullanıcılarınızın kodu şaşırtıcı şekilde davranır.

Atama operatörü

Görevle ilgili söylenecek çok şey var. Ancak, çoğu zaten GMan'ın ünlü Kopyala-Takas SSS, bu yüzden burada en çok atlayacağım, sadece referans için mükemmel ödev operatörünü listeleyeceğim:

X& X::operator=(X rhs)
{
  swap(rhs);
  return *this;
}

Bitshift Operatörleri (Stream I / O için kullanılır)

Bitshift operatörleri << ve >>Yine de, C'den miras aldıkları bit-manipülasyon fonksiyonları için donanım arabiriminde kullanılsa da, çoğu uygulamada aşırı akım giriş ve çıkış operatörleri olarak daha yaygın hale gelmiştir. Bit manipülasyon operatörleri olarak rehberlik aşırı yüklenmesi için, İkili Aritmetik Operatörleri üzerindeki aşağıdaki bölüme bakın. Nesneniz iostreams ile kullanıldığında kendi özel biçiminizi ve ayrıştırma mantığınızı uygulamak için devam edin.

En yaygın olarak aşırı yüklenmiş operatörler arasından akış operatörleri, sözdiziminin üye veya üye olmamakla ilgili bir kısıtlama koymadığı ikili infix operatörleridir. Sol argümanlarını değiştirdikleri için (akış durumunu değiştirirler), başparmak kurallarına göre, sol işleneninin türünün üyeleri olarak uygulanmaları gerekir. Bununla birlikte, sol işlenenleri standart kitaplıktan akışlardır ve standart kitaplık tarafından tanımlanan akış çıkışı ve giriş işleçlerinin çoğu, aslında kendi sınıflarınız için çıkış ve giriş işlemlerini uygularken akış sınıflarının üyeleri olarak tanımlanır. standart kütüphanenin akış türlerini değiştiremez. Bu nedenle, bu operatörleri kendi türleriniz için üye olmayan işlevler olarak uygulamanız gerekir. İkisinin kanonik formları şunlardır:

std::ostream& operator<<(std::ostream& os, const T& obj)
{
  // write obj to stream

  return os;
}

std::istream& operator>>(std::istream& is, T& obj)
{
  // read obj from stream

  if( /* no valid object of T found in stream */ )
    is.setstate(std::ios::failbit);

  return is;
}

Uygularken operator>>Akışın durumunu manuel olarak ayarlamak yalnızca okunmanın kendisi başarılı olduğunda gereklidir, ancak sonuç beklenenin bir sonucu değildir.

Fonksiyon çağrısı operatörü

Functor olarak da bilinen işlev nesneleri oluşturmak için kullanılan işlev çağrısı operatörü, üye işlev, bu yüzden her zaman örtülüdür this üye fonksiyonlarının argümanı. Bunun dışında sıfır da dahil olmak üzere herhangi bir sayıda ek argüman almak için aşırı yüklenebilir.

İşte sözdizimi örneği:

class foo {
public:
    // Overloaded call operator
    int operator()(const std::string& y) {
        // ...
    }
};

Kullanımı:

foo f;
int a = f("hello");

C ++ standart kitaplığı boyunca, işlev nesneleri her zaman kopyalanır. Kendi işlev nesneleriniz bu nedenle kopyalanması için ucuz olmalıdır. Bir işlev nesnesinin kopyalanması pahalı olan verileri kullanması gerekiyorsa, bu verileri başka bir yerde saklamak ve işlev nesnesine başvurmak daha iyidir.

Karşılaştırma operatörleri

İkili infix karşılaştırma operatörleri, başparmak kurallarına göre üye olmayan fonksiyonlar olarak uygulanmalıdır.1. Tekil önek olumsuzlaması ! (aynı kurallara göre) üye fonksiyon olarak uygulanmalıdır. (ama genellikle onu yüklemek için iyi bir fikir değildir.)

Standart kütüphanenin algoritmaları (ör. std::sort()) ve türleri (ör. std::map) her zaman sadece bekler operator< mevcut olması. Ancak türünüzün kullanıcıları, diğer tüm operatörlerin mevcut olmasını beklersiz de tanımlayın operator<, operatör aşırı yüklenmesinin üçüncü temel kuralını takip ettiğinizden ve diğer tüm boole karşılaştırma operatörlerini tanımladığınızdan emin olun. Bunları uygulamak için kanonik yolu şudur:

inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs){return !operator==(lhs,rhs);}
inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs){return  operator< (rhs,lhs);}
inline bool operator<=(const X& lhs, const X& rhs){return !operator> (lhs,rhs);}
inline bool operator>=(const X& lhs, const X& rhs){return !operator< (lhs,rhs);}

Burada dikkat edilmesi gereken önemli nokta, bu operatörlerden sadece ikisinin bir şey yapması, diğerlerinin de bu iki eylemden herhangi birini yapmak için argümanlarını iletmeleridir.

Kalan ikili boole işleçleri aşırı yükleme sözdizimi (||, &&) karşılaştırma operatörlerinin kurallarını takip eder. Ancak öyle çok bunlar için makul bir kullanım durumu bulacağınız ihtimal dışı2.

1  Her türlü kuralda olduğu gibi, bazen de bunu kırmanın sebepleri olabilir. Eğer öyleyse, üye işlevler için ikili karşılaştırma operatörlerinin soldaki işlenenini unutmayınız. *this, olması gerekir constayrıca Dolayısıyla, üye işlev olarak uygulanan bir karşılaştırma operatörünün bu imzanın olması gerekir:

bool operator<(const X& rhs) const { /* do actual comparison with *this */ }

(Not const sonunda.)

2  Yerleşik sürümü olduğuna dikkat edilmelidir. || ve && kısayol semantiği kullanın. Kullanıcı tanımlıyken (yöntem çağrıları için sözdizimsel şeker oldukları için) kısayol semantiği kullanmazlar. Kullanıcı bu işleçlerin kısayol semantiğine sahip olmasını bekler ve kodları buna bağlı olabilir, Bu yüzden onları tanımlamak ASLA tavsiye edilir.

Aritmetik operatörler

Tekli aritmetik operatörler

Tekli artış ve eksiltme operatörleri hem önek hem de postfix tadıyla gelir. Birinden diğerini söylemek gerekirse, postfix türevleri ek bir dummy int argümanı alır. Artış veya azalmayı aşırı yüklerseniz, her zaman hem önek hem de sonek sürümlerini uyguladığınızdan emin olun. Artışların kurallı bir şekilde uygulanması, azaltma aynı kuralları takip eder:

class X {
  X& operator++()
  {
    // do actual increment
    return *this;
  }
  X operator++(int)
  {
    X tmp(*this);
    operator++();
    return tmp;
  }
};

Postfix varyantının önek olarak uygulandığını unutmayın. Ayrıca, postfix'in fazladan bir kopyası olduğunu unutmayın.2

Aşırı yükleme eksi ve artı çok yaygın değildir ve muhtemelen en iyi şekilde kaçınılır. Gerekirse, muhtemelen üye fonksiyonları olarak aşırı yüklenmelidir.

2  Ayrıca, postfix varyantının daha fazla iş yaptığını ve bu nedenle önek varyantından daha az verimli olduğunu unutmayın. Bu genellikle, postfix artışından önce önek artışını tercih etmenin iyi bir nedenidir. Derleyiciler genellikle yerleşik türler için postfix artışının ek çalışmasını optimize ederken, aynı zamanda kullanıcı tanımlı türler için de bunu yapamayabilirler (bu, bir liste yineleyicisi olarak masumca bir şey olabilir). Eskiden alışınca i++hatırlamak çok zor oluyor ++i yerine i yerleşik bir türden değildir (artı bir tür değiştirirken kodu değiştirmeniz gerekir), bu nedenle, postfix'e açıkça gerekmedikçe, her zaman önek artışını kullanma alışkanlığı kazandırmak daha iyidir.

İkili aritmetik operatörler

İkili aritmetik operatörler için, üçüncü temel kural operatörünün aşırı yüklenmesine uymayı unutmayın: +, ayrıca sağlamak +=eğer sağlarsan -, atlamayın -=vb. Andrew Koenig, bileşik atama işleçlerinin bileşik olmayan meslektaşları için bir üs olarak kullanılabileceğini ilk gözlemleyen kişi olduğu söylenir. Yani operatör + açısından uygulanmaktadır +=, - açısından uygulanmaktadır -= vb.

Bizim kurallarımıza göre, + ve onun refakatçileri, üye olmayan meslektaşları iken, üye olmayanlar olmalıdır.+= vb.), sol argümanlarını değiştirmek, üye olmalıdır. İşte örnek kod += ve +Diğer ikili aritmetik operatörler aynı şekilde uygulanmalıdır:

class X {
  X& operator+=(const X& rhs)
  {
    // actual addition of rhs to *this
    return *this;
  }
};
inline X operator+(X lhs, const X& rhs)
{
  lhs += rhs;
  return lhs;
}

operator+= referans başına sonucunu döndürür, operator+ sonucunun bir kopyasını döndürür. Tabii ki, bir referans döndürmek genellikle bir kopyasını iade etmekten daha verimlidir. operator+kopyalama işleminin hiçbir yolu yoktur. Yazarken a + b, sonucun yeni bir değer olmasını bekliyorsunuz, bu yüzden operator+ yeni bir değer döndürmek zorunda.3 Ayrıca dikkat edin operator+ sol işlenenini alır kopyala const referansı yerine. Bunun sebebi, neden veren sebeplerle aynıdır. operator= kopya başına argümanını almak.

Bit manipülasyon operatörleri ~  &  |  ^  <<  >> aritmetik operatörler ile aynı şekilde uygulanmalıdır. Ancak, (aşırı yükleme hariç << ve >> çıkış ve giriş için) aşırı yüklenme için çok az makul kullanım durumu vardır.

3  Yine bundan alınacak ders budur. a += b genel olarak, daha verimli a + b ve mümkünse tercih edilmelidir.

Dizi Aboneliği

Dizi abonesi operatörü, bir sınıf üyesi olarak uygulanması gereken bir ikili işleçtir. Veri öğelerine bir anahtarla erişime izin veren kapsayıcı türler için kullanılır. Bunları sağlamanın kanonik şekli şudur:

class X {
        value_type& operator[](index_type idx);
  const value_type& operator[](index_type idx) const;
  // ...
};

Sınıfınızın kullanıcılarının tarafından döndürülen veri öğelerini değiştirmesini istemediğiniz sürece operator[] (Bu durumda, const olmayan varyantı atlayabilirsiniz), her zaman operatörün her iki değişkenini de sağlamalısınız.

Value_type öğesinin yerleşik bir türe başvurduğu biliniyorsa, operatörün const varyantı bir const referansı yerine bir kopyasını döndürmelidir.

İşaretçi benzeri Tipler için Operatörler

Kendi yineleyicileri veya akıllı işaretleyicileri tanımlamak için, tekil önek dereference operatörünü aşırı yüklemeniz gerekir * ve ikili infix işaretçisi üye erişim operatörü ->:

class my_ptr {
        value_type& operator*();
  const value_type& operator*() const;
        value_type* operator->();
  const value_type* operator->() const;
};

Bunların da neredeyse her zaman bir const'a ve const olmayan bir sürümüne ihtiyaç duyacağını unutmayın. İçin -> operatör, eğer value_type olduğunu class (veya struct veya union) başka bir operator->() kadar, yinelemeli olarak adlandırılır operator->() sınıf dışı bir değer döndürür.

Tek adresli operatör asla aşırı yüklenmemelidir.

İçin operator->*() görmek bu soru. Nadiren kullanılır ve bu nedenle nadiren aşırı yüklüdür. Aslında, yineleyiciler bile aşırı yüklemez.


Devam etmek Dönüşüm Operatörleri


898
2017-12-12 12:47



operator->() aslında son derece tuhaf. Bir geri dönmek için gerekli değildir value_type* - Aslında, başka bir sınıf türü döndürebilir, sınıf türünde bir operator->()Daha sonra çağrılacak. Bu yinelemeli çağrı operator->()s kadar devam eder value_type* dönüş tipi oluşur. Delilik! :) - j_random_hacker
İşaretçi benzeri işleçlerinizin const / non-const sürümlerine katılmıyorum. const const_type & operator * () const; `- bu bir T* const geri dönen const T& Bu durumda, dereferencing üzerinde. Veya başka bir deyişle: bir const işaretçisi bir const pointee anlamına gelmez. Aslında, taklit etmek için önemsiz değildir T const * - bütünün nedeni budur const_iterator standart kitaplıktaki şeyler. Sonuç: İmza olmalı reference_type operator*() const; pointer_type operator->() const - Arne Mertz
Bir yorum: Önerilen ikili aritmetik işleçlerin uygulanması, olabildiğince verimli değildir. Se Boost operatörleri üstbilgileri simmetri notu: boost.org/doc/libs/1_54_0/libs/utility/operators.htm#symmetry İlk parametrenin yerel bir kopyasını kullanırsanız, + = yapın ve yerel kopyayı döndürürseniz bir kopya daha önlenebilir. Bu NRVO optimizasyonunu mümkün kılar. - Manu343726
Sohbette bahsettiğim gibi, L <= R ayrıca ifade edilebilir !(R < L) yerine !(L > R). Optimize edilmesi zor olan ifadelerde (ayrıca Boost.Operators uygulamasının bunu nasıl uyguladığı da) ek bir katman katmanını kaydedebilirsiniz. - TemplateRex
@thomthom: Bir sınıfın durumu için herkese açık olarak erişilebilir bir API yoksa, bir üyeye veya bir devlete erişmek için gereken her şeyi yapmanız gerekir. friend sınıfın. Bu, elbette, tüm operatörler için de geçerlidir. - sbi


C ++ 'da Operatör Aşırı Yüklemenin Üç Temel Kuralları

C ++ 'da operatör aşırı yüklendiğinde, orada izlemeniz gereken üç temel kural. Bütün bu kurallarda olduğu gibi, gerçekten istisnalar vardır. Bazen insanlar onlardan saptılar ve sonuç kötü bir kod değildi, ancak bu tür pozitif sapmalar az ve çok uzaktı. En azından, gördüğüm 100 sapmanın 99'u haksızdı. Bununla birlikte, 1000’den 999’u bile almış olabilirsiniz. Yani aşağıdaki kurallara uymanız daha iyi olacaktır.

  1. Bir operatörün anlamı açıkça belli olmadığı ve tartışmasız olduğu zaman, aşırı yüklenmemelidir.  Bunun yerine, iyi seçilmiş bir isimle bir işlev sağlayın.
    Temel olarak, aşırı yüklenme operatörleri için ilk ve en önemli kural, şöyle diyor: Yapma. Bu garip görünebilir, çünkü operatörün aşırı yüklenmesi hakkında çok şey biliniyor ve bu yüzden birçok makale, kitap bölümü ve diğer metinler bunlarla ilgileniyor. Fakat bu bariz kanıtlara rağmen, Operatörün aşırı yüklenmesinin uygun olduğu, şaşırtıcı derecede az sayıda vaka var.. Bunun nedeni, operatörün uygulama alanındaki kullanımının iyi bilinmediği ve tartışmasız olmadığı sürece, bir operatörün uygulanmasının ardındaki anlamların anlaşılmasının zor olmasıdır. Popüler inanışın aksine, bu neredeyse hiç de zor değil.

  2. Her zaman operatörün iyi bilinen semantiklerine sadık kalın.
    C ++, aşırı yüklenmiş operatörlerin semantikleri üzerinde herhangi bir sınırlama getirmez. Derleyiciniz, ikili sistemi uygulayan kodu mutlu bir şekilde kabul edecektir + operatörün sağ işlenenden çıkarılması. Ancak, böyle bir operatörün kullanıcıları asla ifadeden şüphelenmezlerdi a + b çıkarmak için a itibaren b. Tabii ki, bu, uygulama alanındaki operatörün semantiklerinin tartışmasız olduğunu varsayar.

  3. Her zaman bir dizi ilgili işlemden sağlayın.
    Operatörler birbiriyle ilişkilidirve diğer operasyonlara. Türünüzü destekliyorsa a + bkullanıcılar aramayı bekleyecektir a += bayrıca Önek artışını destekliyorsa ++a, bekleyecekler a++ iyi çalışmak. Eğer kontrol edebilirlerse a < bAyrıca, kesinlikle kontrol edip etmeyeceklerini de a > b. Türünüzü kopyalayabilirlerse, çalışma için de görev beklerler.


Devam etmek Üye ile Üye Olmayanlar Arasındaki Karar.


441
2017-12-12 12:45



Bunlardan herhangi birini ihlal ettiğini bildiğim tek şey şu: boost::spirit lol. - Billy ONeal
@Billy: Bazılarına göre, kötüye kullanma + Dize birleştirme için bir ihlalidir, ancak şu anda iyi kurulmuş praxis haline gelmiştir, böylece doğal görünüyor. Her ne kadar ben bir ev demeti dizisini hatırlamam olsa da, 90'lı yıllarda ikili kullanılan &Bu amaçla (kurulmuş praxis için BASIC'e başvurularak). Ama, evet, onu std lib'e yerleştirmek bunu temel olarak taşa koydu. Aynı şey suistimal için de geçerli << ve >> IO için, BTW. Neden sola kayma açıkça çıkış işlemi olur? Çünkü ilk "Merhaba, dünya!" Yı gördüğümüzde hepimiz bunu öğrendik. uygulama. Ve başka hiçbir sebepten dolayı. - sbi
@curiousguy: Açıklamak zorunda kalırsanız, açık ve tartışmasız değildir. Aynı şekilde aşırı yüklenmeyi tartışmanız veya savunmanız gerekiyorsa. - sbi
@sbi: "arkadaş incelemesi" her zaman iyi bir fikirdir. Benim için kötü bir şekilde seçilen operatör, kötü seçilmiş bir işlev adından farklı değildir (çok gördüm). Operatör sadece işlevlerdir. Ne fazla ne az. Kurallar aynıdır. Ve bir fikrin iyi olup olmadığını anlamak için, en iyi yol anlaşılmak için ne kadar sürdüğünü anlamaktır. (Dolayısıyla, akran değerlendirmesi bir zorunluluktur, ancak akranlar dogmasız ve önyargısız insanlar arasından seçilmelidir.) - Emilio Garavaglia
@sbi Benim için, sadece kesinlikle bariz ve tartışılmaz gerçek operator== Bir denklik ilişkisi olması gerektiğidir (IOW, sinyal vermeyen NaN kullanmamalısınız). Kaplarda çok yararlı eşdeğerlik ilişkileri vardır. Eşitlik ne demektir? "a eşittir b" anlamına gelir a ve b Aynı matematiksel değere sahip olmak. Bir (NaN olmayan) matematiksel değer kavramı float açıktır, ancak bir kabın matematiksel değeri çok farklı (özyinelemeli) yararlı tanımlara sahip olabilir. Eşitliğin en güçlü tanımı "onlar aynı nesnelerdir" ve faydasızdır. - curiousguy


C ++ 'da aşırı yüklenen operatörün Genel Sözdizimi

C ++ 'da yerleşik tipler için operatörlerin anlamını değiştiremezsiniz, operatörler sadece kullanıcı tanımlı tipler için aşırı yüklenebilir1. Yani, işlenenlerden en az biri, kullanıcı tanımlı bir tipte olmalıdır. Diğer aşırı yüklenmiş fonksiyonlarda olduğu gibi, operatörler belirli bir parametre kümesi için sadece bir kez aşırı yüklenebilir.

Tüm operatörler C ++ 'ya aşırı yüklenemez. Aşırı yüklenemeyen operatörler arasında: .  ::  sizeof  typeid  .* ve C ++’daki tek üçlü operatör, ?: 

C ++ 'da aşırı yüklenebilen operatörler arasında şunlar vardır:

  • aritmetik operatörler: +  -  *  /  % ve +=  -=  *=  /=  %= (tüm ikili infix); +  - (tekil önek); ++  -- (unary önek ve postfix)
  • bit manipülasyonu: &  |  ^  <<  >> ve &=  |=  ^=  <<=  >>= (tüm ikili infix); ~ (tekil önek)
  • boole cebiri: ==  !=  <  >  <=  >=  ||  && (tüm ikili infix); ! (tekil önek)
  • bellek yönetimi: new  new[]  delete  delete[]
  • örtük dönüşüm operatörleri
  • miscellany: =  []  ->  ->*  ,  (tüm ikili infix); *  & (tüm tekil önek) () (işlev çağrısı, n-ary infix)

Ancak, aslında kutu Bunların tümünü aşırı yüklemek sizi kastetmiyor meli böyle yap. Operatör aşırı yüklenmesinin temel kurallarına bakınız.

C ++ 'da, operatörler aşırı yüklü özel isimle fonksiyonlar. Diğer işlevlerde olduğu gibi, aşırı yüklenen operatörler genellikle ya bir sol işleneninin türünün üye işlevi veya olarak üye olmayan işlevler. Her ikisini de kullanmakta özgürsünüz ya da bağımlıysanız, çeşitli kriterlere bağlıdır.2 Tek operatör @3x nesnesine uygulandığında, ya operator@(x) veya olarak x.operator@(). Bir ikili infix operatörü @nesnelere uygulandı x ve yolarak adlandırılır operator@(x,y) veya olarak x.operator@(y).4 

Üye olmayan işlevler olarak uygulanan işleçler bazen işleneninin türünün bir arkadaşıdır.

1  “Kullanıcı tanımlı” terimi biraz yanıltıcı olabilir. C ++, yerleşik türler ve kullanıcı tanımlı türler arasındaki ayrımı yapar. İlkine, örneğin int, char ve double; Sonuncusu, standart kütüphaneden olanlar da dahil olmak üzere, kullanıcılar tarafından tanımlanmış olmamakla birlikte, tüm yapı, sınıf, birlik ve enum türlerine aittir.

2  Bu kaplıdır sonraki bölüm bu SSS’nin

3  @ C ++ 'da geçerli bir operatör değil, bu yüzden onu bir yer tutucu olarak kullanıyorum.

4  C ++ 'daki tek çekirdekli operatör aşırı yüklenemez ve yalnızca n-ary operatörünün her zaman bir üye işlevi olarak uygulanması gerekir.


Devam etmek C ++ 'da Operatör Aşırı Yüklemenin Üç Temel Kuralları.


230
2017-12-12 12:46



%= bir "bit manipülasyonu" operatörü değil - curiousguy
~ tekli önek, ikili infix değil. - mrkj
.* yüklenemeyen operatörler listesinden eksik. - celticminstrel
@celticminstrel: Gerçekten de, ve hiç kimse 4.5 yıl boyunca fark etmedi ... Onu işaretlediğiniz için teşekkürler. - sbi
@ H.R .: Bu kılavuzu okudun mu, neyin yanlış olduğunu biliyordun. Genel olarak, sorudan bağlanan ilk üç cevabı okumalısınız. Bu, hayatınızın yarım saatten fazla olmamalı ve size temel bir anlayış kazandırır. Operatöre özgü sözdizimi sonra daha sonra bakabilirsiniz. Özel probleminiz aşırı yüklemeye çalıştığınızı gösteriyor operator+() Bir üye işlevi olarak, ancak özgür bir işlevin imzasını verdi. Görmek İşte. - sbi


Üye ile Üye Olmayanlar Arasındaki Karar

İkili operatörler = (Atama), [] (dizi aboneliği), -> (üye girişi) yanı sıra n-ary ()(işlev çağrısı) operatörü, her zaman olarak uygulanmalıdır üye işlevleriÇünkü dilin sözdizimi onları gerektirir.

Diğer operatörler, üye olarak veya üye olmayanlar olarak uygulanabilir. Bununla birlikte, bunların bazıları genellikle üye olmayan işlevler olarak uygulanmalıdır, çünkü sol işlenenleri sizin tarafınızdan değiştirilemez. Bunların en önemlileri giriş ve çıkış operatörleridir. << ve >>sol işlenenleri, değiştirilemeyen standart kitaplıktan akış sınıfları olan.

Onları üye işlevi veya üye olmayan bir işlev olarak uygulamak için seçmeniz gereken tüm operatörler için, aşağıdaki başparmak kurallarını kullanın karar vermek:

  1. Eğer bir tek operatör, bunu bir üye işlevi.
  2. Bir ikili operatör davranırsa her iki işlenen eşit olarak (bunları değiştirmez), bu operatörü üye olmayan işlevi.
  3. Bir ikili operatör varsa değil her iki işlenenini de tedavi etmek aynı derecede (genellikle sol işleneni değiştirir), bunu yapmak yararlı olabilir üye işlenenin özel bölümlerine erişmesi gerekiyorsa, sol işleneninin türünün işlevi.

Elbette, tüm kurallarda olduğu gibi, istisnalar vardır. Eğer bir tipin varsa

enum Month {Jan, Feb, ..., Nov, Dec}

ve bunun için artırma ve eksiltme işleçlerini aşırı yüklemek istiyorsanız, bunu üye işlevleri olarak yapamazsınız, çünkü C ++ 'da, enum türlerinin üye işlevleri olamaz. Yani bunu serbest bir işlev olarak yüklemelisiniz. Ve operator<() Sınıf şablonunda iç içe geçmiş bir sınıf şablonu için sınıf tanımında satır içi üye işlevi olarak yapıldığında yazılması ve okunması daha kolaydır. Ama bunlar gerçekten nadir istisnalar.

(Ancak, Eğer bir istisna yaparsınız, sorununu unutma constişlenen için, üye işlevleri için örtük olma durumu this argüman. Operatör üye olmayan bir işlev olarak en sol argümanını const referans, bir üye işlevi ile aynı operatörün bir const sonunda yapmak *this bir const referans.)


Devam etmek Ortak operatörler aşırı yüklenmeye.


212
2017-12-12 12:49



Etkili C ++ 'da Herb Sutter'in öğesi (veya C ++ Kodlama Standartları mı?), Üye olmayan üye olmayan işlevleri üye işlevlerine tercih etmeli ve sınıfın kapsüllemesini arttırmalıdır. IMHO, kapsülleme sebebi, üstünlük kuralınıza göre önceliğe sahiptir, ancak başparmak kuralınızın kalite değerini azaltmaz. - paercebal
@paercebal: Etkili C ++ Meyers tarafından C ++ Kodlama Standartları Sutter tarafından. Hangisini kastediyorsun? Her neyse, fikrini sevmiyorum, operator+=() üye olmamak Sol işlenenini değiştirmek zorundadır, bu yüzden tanım gereği içlerine derinliklerine inmek zorundadır. Üye olmamakla ne kazanırsınız? - sbi
@sbi: C ++ Kodlama Standartlarında Madde 44 (Sutter) Üye olmayan arkadaş olmayan işlevleri yazma tercih etTabii ki, sadece bu işlevi yalnızca sınıfın genel arabirimini kullanarak yazabiliyorsanız geçerlidir. Eğer yapamazsanız (ya da performansı kötü bir şekilde engelleyebilirse), ya üye veya arkadaş edinmelisiniz. - Matthieu M.
@sbi: Oops, Etkili, Olağanüstü ... Hayır, isimleri karıştırıyorum. Her halükarda kazanç, bir nesneye özel / korumalı verilere erişen işlevlerin sayısını mümkün olduğunca sınırlandırmaktır. Böylece sınıfınızın kapsüllemesini artırarak bakım / test / evrimi kolaylaştırırsınız. - paercebal
@sbi: Bir örnek. Diyelim ki, her ikisi de bir String sınıfı kodluyorsunuz operator += ve append yöntemleri. append yöntem daha eksiksizdir, çünkü parametrenin bir alt dizgesini index i'den index-1'e ekleyebilirsiniz: append(string, start, end) Sahip olmak mantıklı görünüyor += çağrı ekle start = 0 ve end = string.size. O anda, ek bir üye yöntemi olabilir, ancak operator += üye olmanıza gerek yok ve üye olmayan yapmak String dizileri ile oynayan kod miktarını azaltır, bu yüzden iyi bir şey .... ^ _ ^ ... - paercebal


Dönüşüm Operatörleri (Kullanıcı Tanımlı Dönüşümler olarak da bilinir)

C ++ uygulamasında, derleyicinin türleriniz ve diğer tanımlı türler arasında dönüşmesine izin veren operatörler, dönüşüm operatörleri oluşturabilirsiniz. İki tür dönüşüm operatörü vardır, örtülü ve açık olanlar.

Kapalı Dönüşüm Operatörleri (C ++ 98 / C ++ 03 ve C ++ 11)

Bir örtük dönüşüm işleci, derleyicinin dolaylı olarak dönüştürmesini sağlar. int ve long) kullanıcı tanımlı türün değeri başka bir türe.

Aşağıdaki, örtük bir dönüşüm işlecine sahip basit bir sınıftır:

class my_string {
public:
  operator const char*() const {return data_;} // This is the conversion operator
private:
  const char* data_;
};

Tek değişkenli yapıcılar gibi örtük dönüşüm operatörleri, kullanıcı tanımlı dönüşümlerdir. Derleyiciler, bir aramayı aşırı yüklenen bir işlevle eşleştirmeye çalışırken bir kullanıcı tanımlı dönüştürmeyi kabul eder.

void f(const char*);

my_string str;
f(str); // same as f( str.operator const char*() )

İlk başta bu çok yararlı görünüyor, ancak bununla ilgili problem, örtük dönüşümün beklenmedik bir zamanda başlamasıdır. Aşağıdaki kodda, void f(const char*)çünkü çağrılacak my_string() bir değil lvalueYani ilk eşleşmiyor:

void f(my_string&);
void f(const char*);

f(my_string());

Yeni başlayanlar kolayca bu yanlış ve hatta deneyimli C ++ programcıları bazen şaşırırlar çünkü derleyici şüphelenmedikleri bir aşırı yük alır. Bu sorunlar, açık dönüşüm operatörleri tarafından azaltılabilir.

Açık Dönüşüm Operatörleri (C ++ 11)

Gizli dönüşüm operatörlerinin aksine, açık dönüşüm operatörleri, onları beklemediğinizde asla başlamaz. Aşağıdaki, açık bir dönüşüm operatörüne sahip basit bir sınıftır:

class my_string {
public:
  explicit operator const char*() const {return data_;}
private:
  const char* data_;
};

Dikkat edin explicit. Artık, beklenmedik kodu örtülü dönüştürme işleçlerinden çalıştırmaya çalıştığınızda, derleyici hatası alırsınız:

prog.cpp: İşlevde main ana main () ’:
prog.cpp: 15: 18: hata: ‘f (my_string) çağrısı için eşleme işlevi yok
prog.cpp: 15: 18: not: adaylar:
prog.cpp: 11: 10: not: void f (my_string &)
prog.cpp: 11: 10: not: ‘my_string’ den ‘my_string &’ değerine argüman 1 için bilinen bir dönüşüm yok
prog.cpp: 12: 10: not: void f (const char *)
prog.cpp: 12: 10: not: ‘my_string’ den ‘const char * 'a argüman 1 için bilinen bir dönüşüm yok

Açık döküm operatörünü çağırmak için static_castC stili bir döküm veya bir kurucu stili döküm (örn. T(value) ).

Bununla birlikte, bunun bir istisnası vardır: Derleyici, örtülü olarak dönüştürülmesine izin verilir. bool. Ayrıca, derleyicinin, dönüştürdükten sonra başka bir gizli dönüşüm gerçekleştirmesine izin verilmez. bool (bir derleyicinin bir seferde 2 örtük dönüşüm yapmasına izin verilir, ancak yalnızca 1 kullanıcı tanımlı dönüşüm).

Çünkü derleyici "geçmiş" yapmaz boolAçık dönüşüm operatörleri artık ihtiyacı ortadan kaldırıyor Güvenli Bool deyimi. Örneğin, C ++ 11'den önceki akıllı işaretçiler, tümleşik türlere dönüşümleri önlemek için Safe Bool deyimini kullandı. C ++ 11'de, akıllı işaretçiler açık bir işleç kullanırlar; çünkü derleyici, bir türün açıkça bir türünü boole dönüştürdükten sonra birleşik bir türe dönüştürülmesine izin verilmez.

Devam etmek fazla yükleme new ve delete.


144
2018-05-17 18:32





fazla yükleme new ve delete

Not: Bu sadece ile ilgilidir sözdizimi aşırı yükleme new ve deletedeğil uygulama aşırı yüklenmiş operatörlerin Ben aşırı yükleme semantiğini düşünüyorum new ve delete kendi SSS haklarınıOperatörün aşırı yüklenmesi konusu içerisinde asla adalet yapamam.

temeller

C ++ yazdığınızda yeni ifade sevmek new T(arg) Bu ifade değerlendirildiğinde iki şey olur: Önce operator new ham bellek elde etmek için çağrılır ve daha sonra uygun kurucu T bu ham belleği geçerli bir nesneye dönüştürmek için çağrılır. Benzer şekilde, bir nesneyi sildiğinizde, önce onun yıkıcısı çağrılır ve daha sonra bellek operator delete.
C ++, bu işlemlerin her ikisini de ayarlamanıza izin verir: bellek yönetimi ve ayrılan bellekteki nesnenin inşası / imhası. İkincisi, bir sınıf için kurucular ve yıkıcılar yazarak yapılır. İnce ayarlı bellek yönetimi, kendi yazarak yapılır operator new ve operator delete.

Operatör aşırı yüklenmesinin temel kurallarından ilki - yapma - özellikle aşırı yükleme için geçerlidir new ve delete. Bu operatörleri aşırı yüklemenin neredeyse tek nedeni performans sorunları ve hafıza kısıtlamalarıve çoğu durumda, algoritmalarda değişiklikler kullanılmış, çok sağlayacak daha yüksek maliyet / kazanç oranı Bellek yönetimini düzeltmeye çalışmaktan daha iyi.

C ++ standart kütüphanesi önceden tanımlanmış bir dizi ile birlikte gelir new ve delete operatörler. En önemlileri bunlar:

void* operator new(std::size_t) throw(std::bad_alloc); 
void  operator delete(void*) throw(); 
void* operator new[](std::size_t) throw(std::bad_alloc); 
void  operator delete[](void*) throw(); 

İlk iki nesne için bir nesne, ikinci nesne için bir nesne ayırır / ayırır. Bunların kendi versiyonlarını verirseniz, onlar aşırı yüklenmeyin, ancak yerine standart kütüphaneden olanlar.
Aşırı yüklerseniz operator new, her zaman eşleştirmeyi de aşırı yüklemelisiniz operator deleteasla arama niyetinde olmasan bile. Bunun nedeni, eğer bir kurucu yeni bir ifadenin değerlendirmesi sırasında atarsa, çalışma zamanı sistemi belleği belleğe geri döndürecektir. operator delete eşleme operator new Nesneyi oluşturmak için belleği ayırmak için çağrıldı. Eşleşme sağlamazsanız operator delete, varsayılan bir, neredeyse her zaman yanlış olan denir.
Aşırı yüklerseniz new ve delete, dizi değişkenlerini de aşırı yüklemelisiniz.

Yerleştirme new

C ++, yeni ve silinmiş işleçlerin ek argümanlar almasına izin verir.
Yeni yerleşim olarak adlandırılan yeni bir adres, şu adrese aktarılan belirli bir adreste bir nesne oluşturmanıza olanak tanır:

class X { /* ... */ };
char buffer[ sizeof(X) ];
void f()
{ 
  X* p = new(buffer) X(/*...*/);
  // ... 
  p->~X(); // call destructor 
} 

Standart kütüphane, bunun için yeni ve silme operatörlerinin uygun aşırı yüklenmeleriyle birlikte gelir:

void* operator new(std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete(void* p,void*) throw(); 
void* operator new[](std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete[](void* p,void*) throw(); 

Yukarıda verilen yeni yerleşim kod örneğinde, operator delete X'in kurucusu bir istisna atarsa ​​asla çağrılmaz.

Ayrıca aşırı yükleyebilirsiniz new ve delete diğer argümanlar ile. Yeni yerleşim için ek argümanda olduğu gibi, bu argümanlar da anahtar kelimeden sonra parantez içinde listelenir new. Sadece tarihsel nedenlerden ötürü, bu tür varyantlar genellikle argümanları belirli bir adrese bir nesne koymak için olmasalar bile yeni yerleşim olarak da adlandırılır.

Sınıfa özgü yeni ve sil

En yaygın olarak, bellek yönetimini ince ayar yapmak isteyeceksiniz çünkü ölçüm, belirli bir sınıfın veya ilgili sınıfların örneklerinin sık sık oluşturulduğunu ve yok edildiğini ve çalışma zamanı sisteminin varsayılan bellek yönetiminin, Genel performans, bu özel durumdaki verimsiz fırsatlar. Bunu iyileştirmek için yeni bir aşırı yükleme yapabilir ve belirli bir sınıfa yazabilirsiniz:

class my_class { 
  public: 
    // ... 
    void* operator new();
    void  operator delete(void*,std::size_t);
    void* operator new[](size_t);
    void  operator delete[](void*,std::size_t);
    // ... 
}; 

Bu nedenle aşırı yüklenen, yeni ve sil, statik üye işlevleri gibi davranır. Nesneleri için my_class, std::size_t argüman her zaman sizeof(my_class). Bununla birlikte, bu operatörler ayrıca dinamik olarak ayrılmış nesneler için çağrılır türetilmiş sınıflarBu durumda bundan daha büyük olabilir.

Global yeni ve sil

Global yenisini silmek ve silmek için, standart kütüphanenin önceden tanımlanmış operatörlerini kendi başımıza yazmanız yeterlidir. Ancak, bu nadiren yapılması gerekiyor.


131
2017-12-12 13:07



Ayrıca, global operatörü yen ve silme işleminin genellikle performans için geçerli olduğuna katılıyorum: Aksine, genellikle hata izleme için. - Yttrill
Ayrıca, aşırı yüklenmiş yeni bir operatör kullanırsanız, eşleşen argümanlara sahip bir silme operatörü sağlamanız gerektiğini de unutmayın. Küresel yeni / silme bölümünde, ilginin fazla olmadığı bölümlerde söylüyorsunuz. - Yttrill
@Yttrill, kafanızı karıştırıyorsunuz. anlam aşırı yüklenir. Ne "operatör aşırı yükleme" anlamı, anlamı aşırıya kaçmış olmasıdır. Bu kelimenin tam anlamıyla fonksiyonların aşırı olduğu anlamına gelmez ve özellikle Yeni operatör standart versiyonunu aşırı yüklemeyecektir. @sbi tam tersini iddia etmez. "Ekstra ekleyen operatör" demek yaygın olduğu için "yeni aşırı yükleme" olarak adlandırılır. - Johannes Schaub - litb
@sbi: Gör (veya daha iyisi, bağlantı) gotw.ca/publications/mill15.htm . Bazen kullanan insanlara karşı sadece iyi bir uygulamadır nothrow yeni. - Alexandre C.
Msgstr "Eğer eşleşen bir işleç silme sağlamazsanız, varsayılan olanı çağırırsınız" -> Aslında, herhangi bir bağımsız değişken eklediğinizde ve eşleşen bir silme oluşturmazsanız, hiçbir operatör silme çağrılmıyor ve bir bellek sızıntınız var demektir. (15.2.2, nesne tarafından depolanan depolama, yalnızca uygun bir ... operatör silme bulunması durumunda ayrılır) - dascandy


Neden olamaz operator<< akış nesneleri için işlev std::cout ya da bir dosyaya üye bir işlev mi?

Diyelim ki:

struct Foo
{
   int a;
   double b;

   std::ostream& operator<<(std::ostream& out) const
   {
      return out << a << " " << b;
   }
};

Verilen, kullanamazsınız:

Foo f = {10, 20.0};
std::cout << f;

Dan beri operator<< üye işlevi olarak aşırı yüklenir FooOperatörün LHS'si bir Foo nesne. Yani, kullanmanız gerekecek:

Foo f = {10, 20.0};
f << std::cout

çok sezgisel olmayan.

Üye olmayan bir işlev olarak tanımlarsanız,

struct Foo
{
   int a;
   double b;
};

std::ostream& operator<<(std::ostream& out, Foo const& f)
{
   return out << f.a << " " << f.b;
}

Kullanabileceksiniz:

Foo f = {10, 20.0};
std::cout << f;

çok sezgisel olan


29
2018-01-22 19:00