C ++ derleyicileri neden işleç == ve işleç! = Tanımlamıyor?


302

Derleyicinin sizin için olabildiğince fazla çalışmasına izin verme büyük bir hayranıyım. Basit bir sınıf yazarken derleyici size 'ücretsiz' için aşağıdakileri verebilir:

  • Varsayılan (boş) bir yapıcı
  • Bir kopya oluşturucu
  • Bir yıkıcı
  • Bir atama operatörü ( operator=)

Ancak size herhangi bir karşılaştırma operatörü veremiyor gibi görünüyor - operator==veya operator!=. Örneğin:

class foo
{
public:
    std::string str_;
    int n_;
};

foo f1;        // Works
foo f2(f1);    // Works
foo f3;
f3 = f2;       // Works

if (f3 == f2)  // Fails
{ }

if (f3 != f2)  // Fails
{ }

Bunun için iyi bir neden var mı? Neden bir üye-üye karşılaştırması yapmak sorun olur? Eğer sınıf hafızayı ayırırsa, o zaman dikkatli olmak istersiniz, ancak basit bir sınıf için derleyici bunu sizin için yapabilir mi?


4
Tabii ki, yıkıcı da ücretsiz olarak sağlanmaktadır.
Johann Gerell

23
Son görüşmelerinden birinde, Alex Stepanov ==, =belirli bir koşulda varsayılan bir otomatik atama ( ) olduğu gibi , varsayılan bir otomatik olmamanın bir hata olduğunu belirtti . (Mantık için de geçerlidir, çünkü önerilerle ilgili argüman tutarsız =ve ==ve sadece saniye).
alfC

2
@becko A9'daki dizide yer alıyor: youtube.com/watch?v=k-meLQaYP5Y , hangisinin görüşmelerde olduğunu hatırlamıyorum. Ayrıca C ++ 17 için yol açtığı yönünde
alfC 16:16

1
@becko, Youtube'da bulunan A9'da "Bileşenlerle verimli programlama" veya "Konuşmaları Programlama" serilerindeki ilklerden biri.
alfC

1
@becko Aslında Alex'in bakış açısını gösteren bir cevap var stackoverflow.com/a/23329089/225186
alfC

Yanıtlar:


71

Derleyici bir işaretçi karşılaştırması mı yoksa derin (iç) bir karşılaştırma mı istediğinizi bilemez.

Uygulamamak ve programcının bunu kendileri yapmasına izin vermek daha güvenlidir. Sonra istedikleri tüm varsayımları yapabilirler.


292
Bu sorun, onun oldukça zararlı olduğu bir kopyalayıcı oluşturmasını engellemiyor.
MSalters

78
Kopya kurucular (ve operator=karşılaştırma operatörleri aynı bağlamda genel olarak iş) -, sen gerçekleştirildikten sonra yönünde bir beklenti vardır a = b, a == bdoğrudur. Derleyicinin operator==, aynı değer değeri semantiğini kullanarak varsayılan değer vermesi kesinlikle mantıklıdır operator=. Paercebal'in aslında doğru olduğundan şüpheleniyorum operator=(ve kopya ctor) sadece C uyumluluğu için sağlandı ve durumu daha da kötüleştirmek istemediler.
Pavel Minaev

46
-1. Tabii ki derin bir karşılaştırma istiyorsunuz, eğer programcı bir işaretçi karşılaştırması isteseydi, yazardı (& f1 == & f2)
Viktor Sehr

62
Viktor, cevabını tekrar düşünmeni öneririm. Foo sınıfı bir Bar * içeriyorsa, derleyici Foo :: operator =='in Bar * adresini veya Bar içeriğini karşılaştırmak isteyip istemediğini nasıl bilebilir?
Mark Ingram

46
@ İşaret: Eğer bir işaretçi içeriyorsa, işaretçi değerlerini karşılaştırmak mantıklıdır - bir değer içeriyorsa değerleri karşılaştırmak mantıklıdır. İstisnai durumlarda, programcı geçersiz kılabilir. Bu, dilin ints ve pointer-ints arasındaki karşılaştırmayı uyguladığı gibidir.
Eamon Nerbonne

317

Derleyici varsayılan bir kopya oluşturucu sağlayabilirse, benzer bir varsayılan değer sağlayabilmesi argümanı operator==()belirli bir miktar mantıklıdır. Bu operatör için derleyici tarafından oluşturulan bir varsayılan sağlama kararının sebebinin Stroustrup'un "C ++ Tasarım ve Evrimi" nde varsayılan kopya yapıcı hakkında söyledikleriyle tahmin edilebileceğini düşünüyorum (Bölüm 11.4.1 - Kopyalama Kontrolü) :

Şahsen ben kopyalama işlemlerinin varsayılan olarak tanımlanmasını talihsiz görüyorum ve sınıflarımın çoğunun nesnelerinin kopyalanmasını yasaklıyorum. Ancak, C ++ varsayılan atamasını ve kopya yapıcılarını C'den devralmıştır ve sıklıkla kullanılırlar.

Yani "yerine neden C ++ 'ın bir varsayılanı yok operator==() ?" Yerine, "C ++ neden varsayılan bir atama ve kopya oluşturucuya sahip?" Sorusu olmalıdır; yanıt, bu öğelerin C ile geriye doğru uyumluluk için Stroustrup tarafından isteksizce dahil edilmesidir. (muhtemelen C ++ 'nın siğillerinin çoğunun nedeni, aynı zamanda C ++' ın popülaritesinin başlıca nedeni).

Kendi amaçlarım için, IDE'mde yeni sınıflar için kullandığım snippet, özel atama operatörü ve kopya oluşturucu için bildirimler içeriyor, böylece yeni bir sınıf oluşturduğumda varsayılan atama ve kopyalama işlemleri alamıyorum - bildirimi açıkça kaldırmam gerekiyor private:Derleyicinin bunları benim için üretmesini istiyorsanız , bölümden bu işlemlerin .


29
İyi cevap. Sadece atama operatörü ve kopya yapıcısını özel yapmak yerine C ++ 11'de bunları tamamen kaldırabilirsiniz: Foo(const Foo&) = delete; // no copy constructorveFoo& Foo=(const Foo&) = delete; // no assignment operator
karadoc

9
"Ancak, C ++ varsayılan atama ve kopya yapıcıları C'den devralındı" Bu neden ALL C ++ türlerini bu şekilde yapmak zorunda olduğu anlamına gelmez. Bunu sadece eski POD'larla sınırlamış olmalılar, sadece C'deki türler, artık yok.
thesaint

3
C ++ 'nın bu davranışları neden miras aldığını kesinlikle anlayabiliyorum struct, ancak classfarklı (ve akılcı) davranmasına izin vermesini diliyorum . Bu süreçte, varsayılan erişim arasında structve classyanında daha anlamlı bir fark olurdu .
jamesdlin

@jamesdlin Bir kural istiyorsanız, bir dtor bildirilirse örtülü bildirimi ve ctor tanımını ve atamayı devre dışı bırakmak en mantıklı olacaktır.
Deduplicator

1
Hala programcı derleyici oluşturmak için açıkça sipariş izin herhangi bir zarar görmüyorum operator==. Bu noktada, bazı kazan plakası kodları için sadece sözdizimi şekeri. Bu şekilde programlayıcının sınıf alanları arasında bir işaretçi göz ardı edebileceğinden korkuyorsanız, yalnızca eşitlik işleçleri olan ilkel türler ve nesneler üzerinde çalışabileceği bir koşul ekleyebilirsiniz. Yine de buna tamamen izin vermemek için bir neden yok.
NO_NAME

93

C ++ 20'de bile, derleyici hala operator==sizin için örtük olarak üretmeyecektir

struct foo
{
    std::string str;
    int n;
};

assert(foo{"Anton", 1} == foo{"Anton", 1}); // ill-formed

Ancak, C ++ 20'den beri açıkça varsayılan olarak ayarlama becerisi kazanacaksınız :==

struct foo
{
    std::string str;
    int n;

    // either member form
    bool operator==(foo const&) const = default;
    // ... or friend form
    friend bool operator==(foo const&, foo const&) = default;
};

Varsayılan ==, üye olarak yapar ==(varsayılan kopya yapıcısının üye olarak kopya yapması gibi). Yeni kurallar ==ve arasındaki beklenen ilişkiyi de sağlar !=. Örneğin, yukarıdaki beyanname ile her ikisini de yazabilirim:

assert(foo{"Anton", 1} == foo{"Anton", 1}); // ok!
assert(foo{"Anton", 1} != foo{"Anton", 2}); // ok!

Bu özel özelliği (varsaymak operator==ve simetri arasındaki ==ve !=) gelen bir teklifin olduğunu daha geniş dil özelliğinin parçasıydı operator<=>.


Bu konuda daha yeni bir güncelleme olup olmadığını biliyor musunuz? C ++ 17'de kullanılabilir mi?
dcmm88

3
@ dcmm88 Maalesef C ++ 17'de mevcut olmayacak. Cevabı güncelledim.
Anton Savin

2
Aynı şeye izin veren değiştirilmiş bir teklif (kısa form hariç) C ++ 20'de olacak :)
Rakete1111

Temel olarak = default, varsayılan olarak oluşturulmayan şey için belirtmeniz gerekir , değil mi? Bana oksimoron gibi geliyor ("açık varsayılan").
artin

@artin Dile yeni özellikler eklenmesi mevcut uygulamayı bozmamalıdır. Derleyicinin yapabileceği yeni kütüphane standartları veya yeni şeyler eklemek bir şeydir. Daha önce var olmadıkları yerlere yeni üye işlevleri eklemek tamamen farklı bir hikaye. Projenizi hatalardan korumak için çok daha fazla çaba gerekir. Şahsen derleyici bayrağını açık ve kapalı varsayılan arasında geçiş yapmayı tercih ederim. Eski C ++ standardından proje oluşturmak, derleyici bayrağına göre açık varsayılan kullanın. Derleyiciyi zaten güncellediniz, böylece doğru şekilde yapılandırmalısınız. Yeni projeler için bunu örtük yapın.
Maciej Załucki

44

IMHO, "iyi" bir sebep yok. Bu tasarım kararını kabul eden pek çok insanın nedeni, değere dayalı anlambilimin gücüne hakim olmayı öğrenmedikleri içindir. İnsanların uygulamalarında ham işaretçiler kullandıkları için birçok özel kopya oluşturucu, karşılaştırma operatörü ve yıkıcı yazmaları gerekir.

Uygun akıllı işaretçiler (std :: shared_ptr gibi) kullanıldığında, varsayılan kopya oluşturucu genellikle iyidir ve varsayımsal varsayılan karşılaştırma operatörünün açık şekilde uygulanması iyi olacaktır.


39

C ++, C vermediği için yanıt vermedi ve C'nin ilk olarak neden yalnızca varsayılan = = no == sağladığı yanıtlandı. C basit tutmak istedi: C uygulandı = memcpy tarafından; ancak == dolgu nedeniyle memcmp tarafından uygulanamaz. Dolgu başlatılmadığından memcmp, aynı olmalarına rağmen farklı olduklarını söylüyor. Boş sınıf için de aynı sorun vardır: memcmp boş sınıfların boyutu sıfır olmadığından farklı olduklarını söyler. Yukarıdan görülebilir. == uygulamasının C'de uygulamaktan daha karmaşık olduğu görülebilir. Bununla ilgili bazı kod örnekleri . Eğer yanılıyorsam düzeltmeniz takdir edilecektir.


6
C ++ memcpy kullanmaz operator=- bu yalnızca POD türleri için çalışır, ancak C ++ operator=POD olmayan türler için de bir varsayılan sağlar .
Flekso

2
Evet, C ++ daha karmaşık bir şekilde uygulandı. C sadece = basit bir memcpy ile uygulanmış gibi görünüyor.
Rio Wing

Bu cevabın içeriği Michael'ınkiyle bir araya getirilmelidir. Sorunu düzeltir, sonra bu cevap verir.
Sgene9

27

Bu videoda STL'nin yaratıcısı Alex Stepanov bu soruyu 13:00 civarında ele alıyor. Özetlemek gerekirse, C ++ evrimini izledikten sonra şunu savunuyor:

  • == ve! = ' Nin dolaylı olarak beyan edilmemesi talihsiz bir durumdur (ve Bjarne onunla hemfikirdir). Doğru bir dil bu şeyleri sizin için hazır bulundurmalıdır ( == anlamını kıran bir ! = Tanımlayamamanız gerektiğini ileri sürmektedir )
  • Bu davanın kökleri (C ++ problemlerinin çoğu gibi) C'de olmasıdır. Burada, atama operatörü dolaylı olarak bitler halinde atama ile tanımlanır, ancak == için işe yaramaz . Bjarne Stroustrup'un bu makalesinde daha ayrıntılı bir açıklama bulunabilir .
  • Takip eden soruda Neden o zaman üye karşılaştırmasıyla üye değildim , inanılmaz bir şey söylüyor : C biraz kendi kendine yetişen bir dildi ve Ritchie için bu şeyleri uygulayan adam, bunu uygulamak zor olduğunu söyledi!

Daha sonra (uzak) gelecekte == ve ! = Örtük olarak üretileceğini söyler.


2
Bu uzak gelecek 2017, 18 ya da 19 olmayacak gibi gözüküyor, iyi
kaymamı yakalıyorsun

18

C ++ 20, kolayca bir varsayılan karşılaştırma işleci uygulamak için bir yol sağlar.

Cppreference.com'dan bir örnek :

class Point {
    int x;
    int y;
public:
    auto operator<=>(const Point&) const = default;
    // ... non-comparison functions ...
};

// compiler implicitly declares operator== and all four relational operators work
Point pt1, pt2;
if (pt1 == pt2) { /*...*/ } // ok, calls implicit Point::operator==
std::set<Point> s; // ok
s.insert(pt1); // ok
if (pt1 <= pt2) { /*...*/ } // ok, makes only a single call to Point::operator<=>

4
PointBir sipariş operasyonu için örnek olarak kullandıklarına şaşırdım , çünkü iki noktayı xve ykoordinatları sipariş etmenin makul bir varsayılan yolu yok ...
boru

4
@pipe Elemanların hangi sırada olduklarını umursamıyorsanız, varsayılan operatörü kullanmak mantıklıdır. Örneğin, std::settüm noktaların benzersiz olduğundan ve yalnızca std::setkullandığından emin olmak için kullanabilirsiniz operator<.
VLL

Dönüş türü Hakkında auto: For Bu durumda biz hep öyle olacak varsayabiliriz std::strong_orderinggelen #include <compare>?
kevinarpe

1
@kevinarpe Dönüş türü, std::common_comparison_category_tbu sınıf için varsayılan sıralama ( std::strong_ordering) olur.
vll

15

Varsayılan tanımlamak mümkün değildir ==, ancak varsayılan tanımlayabilirsiniz !=aracılığıyla ==genellikle kendinizi tanımlamak gerekir ki. Bunun için aşağıdaki şeyleri yapmalısınız:

#include <utility>
using namespace std::rel_ops;
...

class FooClass
{
public:
  bool operator== (const FooClass& other) const {
  // ...
  }
};

Ayrıntılar için http://www.cplusplus.com/reference/std/utility/rel_ops/ adresini görebilirsiniz .

Ayrıca operator< , tanımlarsanız , <=,>,> = için işleçler kullanılırken bundan çıkarılabilir std::rel_ops.

Ancak kullanırken dikkatli olmalısınız std::rel_ops çünkü karşılaştırma işleçleri beklenmedik türler için çıkarılabilir.

İlgili operatörü temel olandan çıkarmanın daha fazla tercih edilen yolu, boost :: operatörlerini kullanmaktır .

Desteklemede kullanılan yaklaşım daha iyidir, çünkü kapsamdaki tüm sınıflar için değil, yalnızca istediğiniz sınıf için işleç kullanımını tanımlar.

Ayrıca, "+ =", - "- =", vb. Öğelerinden "+" oluşturabilirsiniz ... (tam listeye buradan bakın )


Operatör !=yazdıktan sonra varsayılan alamadım ==. Yoksa yaptım ama eksikti const. Ben de kendim yazmak zorunda kaldım ve her şey iyiydi.
John

gerekli sonuçları elde etmek için sabit bir şekilde oynayabilirsiniz. Kod olmadan neyin yanlış olduğunu söylemek zor.
sergtk

2
Bir sebep var rel_ops C ++ 20 kullanımdan kaldırıldı çünkü: öyle değil çalışır kesinlikle tutarlı, en azından değil her yerde, ve. Derlemenin güvenilir bir yolu yoktur sort_decreasing(). Öte yandan, Boost.Operators çalışır ve her zaman çalıştı.
Barry

10

C ++ 0x gelmiştir diyorsunuz böylece, varsayılan fonksiyonları için bir öneri vardı default operator==; Biz bu şeylerin açık hale getirmek için yardımcı olduğunu öğrendik.


3
Ben sadece "özel üye fonksiyonları" (varsayılan kurucu, kopya kurucu, atama operatörü ve yıkıcı) açıkça varsayılan olabilir düşündüm. Bunu başka operatörlere de yaydılar mı?
Michael Burr

4
Move yapıcısı da varsayılan olarak ayarlanabilir, ancak bunun geçerli olduğunu düşünmüyorum operator==. Yazık ki.
Pavel Minaev

5

Kavramsal olarak eşitliği tanımlamak kolay değildir. POD verileri için bile, alanlar aynı olsa bile, ancak farklı bir nesne (farklı bir adreste) olması gerektiği iddia edilemez. Bu aslında operatörün kullanımına bağlıdır. Ne yazık ki derleyiciniz psişik değil ve bunu çıkaramaz.

Bunun yanı sıra, varsayılan işlevler kendini ayağa vurmanın mükemmel yoludur. Açıkladığınız varsayılanlar temel olarak POD yapılarıyla uyumluluğu korumak için vardır. Bununla birlikte, geliştiriciler onları veya varsayılan uygulamaların anlambilimini unutarak fazlasıyla yıkıma neden olurlar.


10
POD yapıları için herhangi bir belirsizlik yoktur - değer eşitliği (referans eşitliğinden ziyade) olan başka herhangi bir POD türüyle aynı şekilde davranmalıdırlar. intKopya ctoruyla diğerinden yaratılan biri, yaratıldığı şeye eşittir; structiki intalandan biri için yapılacak tek mantıklı şey aynı şekilde çalışmaktır.
Pavel Minaev

1
@mgiuca: Bir değer olarak davranan herhangi bir türün sözlükte veya benzer bir koleksiyonda anahtar olarak kullanılmasına izin verecek evrensel bir eşdeğerlik ilişkisi için önemli bir yarar görebilirim. Bununla birlikte, bu tür koleksiyonlar, garantili-dönüşlü bir eşdeğerlik ilişkisi olmadan yararlı bir şekilde davranamazlar. IMHO, en iyi çözüm, tüm yerleşik türlerin mantıklı bir şekilde uygulayabileceği yeni bir operatör tanımlamak ve bazılarının eşitliği referans eşdeğerliği olarak tanımlaması, bazıları ise hedefin denklik operatörü.
Supercat

1
@supercat Benzetmeyle, +operatör için şamandıralar için ilişkisel olmadığı konusunda neredeyse aynı argümanı yapabilirsiniz ; olduğunu (x + y) + z! = x + (y + z), yolu FP yuvarlama nedeniyle oluşur. (Muhtemelen, bu ==normal sayısal değerler için geçerli olduğundan çok daha kötü bir sorundur .) Tüm sayısal türler (hatta int) için çalışan ve hemen hemen aynı olan +ancak ilişkilendirici ( bir şekilde). Ama o zaman birçok insana gerçekten yardım etmeden dile şişkinlik ve karışıklık eklersiniz.
mgiuca

1
@mgiuca: Uç durumlar dışında oldukça benzer şeylere sahip olmak genellikle son derece yararlıdır ve bu tür şeylerden kaçınmak için yanlış yönlendirilmiş çabalar çok gereksiz karmaşıklığa neden olur. İstemci kodunun bazen bir şekilde ele alınması için kenar durumlara ihtiyacı ve bazen de başka bir şekilde ele alınması gerekiyorsa, her işlem stili için bir yönteme sahip olmak, istemcide çok sayıda kenar durumu işleme kodunu ortadan kaldırır.
Analojinize gelince

1
... bu açıdan bugünkünden daha fazla) ve dolayısıyla imkansızı yapmadıkları bir sürpriz olmamalıdır. Bununla birlikte, kopyalanabilecek her tür değere evrensel olarak uygulanabilecek bir denklik ilişkisinin uygulanmasının önünde temel bir engel yoktur.
supercat

1

Bunun için iyi bir neden var mı? Neden bir üye-üye karşılaştırması yapmak sorun olur?

İşlevsel olarak bir sorun olmayabilir, ancak performans açısından varsayılan üye-üye karşılaştırması, varsayılan üye-üye atama / kopyalama işleminden daha düşük optimal olabilir. Atama sırasından farklı olarak, karşılaştırma sırası performansı etkiler çünkü ilk eşit olmayan üye geri kalanın atlanabileceğini ima eder. Dolayısıyla, genellikle eşit olan bazı üyeler varsa, bunları en son karşılaştırmak istersiniz ve derleyici hangi üyelerin eşit olma olasılığının daha yüksek olduğunu bilmez.

verboseDescriptionNispeten küçük olası hava durumu tanımlarından seçilen uzun bir dizenin olduğu bu örneği düşünün .

class LocalWeatherRecord {
    std::string verboseDescription;
    std::tm date;
    bool operator==(const LocalWeatherRecord& other){
        return date==other.date
            && verboseDescription==other.verboseDescription;
    // The above makes a lot more sense than
     // return verboseDescription==other.verboseDescription
     //     && date==other.date;
    // because some verboseDescriptions are liable to be same/similar
    }
}

(Tabii ki derleyici, yan etkisi olmadığını fark ederse karşılaştırma sırasını göz ardı etme hakkına sahip olacaktır, ancak muhtemelen kuyruğunu, daha iyi bilgi sahibi olmadığı kaynak kodundan alacaktır.)


Ancak, bir performans sorunu bulursanız, hiç kimse sizi en iyi duruma getirilmiş kullanıcı tanımlı bir karşılaştırma yazmaktan alıkoymaz. Deneyimlerime göre bu vakaların küçük bir azınlık olurdu.
Peter - Monica

1

Sadece bu sorunun cevapları zaman geçtikçe tam kalır: C ++ 20'den beri komutla otomatik olarak oluşturulabilir auto operator<=>(const foo&) const = default;

Tüm işleçleri oluşturur: ==,! =, <, <=,> Ve> =, ayrıntılar için bkz. Https://en.cppreference.com/w/cpp/language/default_comparisons .

Operatörün görünümü nedeniyle <=>, buna uzay gemisi operatörü denir. Ayrıca bkz. Neden C ++ uzay gemisi operatörüne ihtiyacımız var? .

DÜZENLEME: Ayrıca C ++ 11 ile bunun için oldukça temiz bir yedek mevcuttur ile tam bir kod örneği için std::tiebkz. Https://en.cppreference.com/w/cpp/utility/tuple/tiebool operator<(…) . Çalışmak için değiştirilen ilginç kısım ==:

#include <tuple>

struct S {
………
bool operator==(const S& rhs) const
    {
        // compares n to rhs.n,
        // then s to rhs.s,
        // then d to rhs.d
        return std::tie(n, s, d) == std::tie(rhs.n, rhs.s, rhs.d);
    }
};

std::tie tüm karşılaştırma operatörleriyle çalışır ve derleyici tarafından tamamen optimize edilmiştir.


-1

Kabul ediyorum, POD tipi sınıflar için derleyici bunu sizin için yapabilirdi. Ancak basit olarak düşünebileceğiniz derleyici yanlış olabilir. Bu yüzden programcının bunu yapmasına izin vermek daha iyidir.

Bir zamanlar iki alanın benzersiz olduğu bir POD vakam vardı - bu yüzden bir karşılaştırma asla doğru kabul edilmeyecekti. Ancak sadece yüke kıyasla ihtiyacım olan karşılaştırma - derleyicinin asla anlamayacağı veya kendi başına çözebileceği bir şey.

Ayrýca - yazmak uzun sürmüyorlar deđil mi ?!

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.