C ++ 'da neden temel sınıf yok?


84

Tasarım açısından bakıldığında, neden C ++ 'da hiç temel sınıf yok, genellikle objectdiğer dillerde ne var ?


27
Bu gerçekten bir tasarım sorusundan çok felsefi bir sorudur. Kısa cevap, "Bjarne görünüşe göre bunu yapmak istemediği için."
Jerry Coffin

22
Kişisel olarak, her şeyin aynı temel sınıftan türetilmiş olmasının bir tasarım hatası olduğunu düşünüyorum. Değerinden daha fazla soruna neden olan bir programlama stili sunar (daha sonra gerçek nesneyi kullanmak için daha sonra dava açmanız gereken genel nesne kaplarına sahip olma eğiliminde olduğunuz için (Bu, kötü tasarım IMHO olarak kaşlarını çatıyorum)). Hem arabaları hem de komut otomasyon nesnelerini barındırabilecek bir konteyner gerçekten istiyor muyum?
Martin York

3
@Martin ama şu şekilde bakın: örneğin, C ++ 0x auto'ya kadar, yineleyiciler için mil uzunluğunda tür tanımları veya bir kerelik typedefs kullanmak zorundaydınız . Daha genel bir sınıf hiyerarşisiyle, sadece objectveya kullanabilirsiniz iterator.
slezica

9
@Santiago: Birleştirilmiş bir tür sistemin kullanılması, neredeyse her zaman, tümü nispeten pahalı olan ve tümü mümkün olan optimizasyonları engelleyen çok biçimliliğe, dinamik gönderime ve RTTI'ye dayanan çok sayıda kodla karşılaştığınız anlamına gelir. kullanılmaz.
James McNellis

2
@GWW - ... herhangi bir STL kapsayıcısı için geçerli olmaması dışında.
Chris Lutz

Yanıtlar:


116

Kesin karar Stroustrup'un SSS'lerinde bulunur . Kısacası, herhangi bir anlamsal anlam taşımaz. Bir maliyeti olacak. Şablonlar, kapsayıcılar için daha kullanışlıdır.

C ++ neden evrensel bir Object sınıfına sahip değil?

  • Birine ihtiyacımız yok: genel programlama, çoğu durumda statik olarak güvenli alternatifler sağlar. Diğer durumlar, çoklu miras kullanılarak ele alınır.

  • Yararlı bir evrensel sınıf yoktur: gerçek bir evrensel kendi başına anlambilim taşımaz.

  • "Evrensel" bir sınıf, türler ve arayüzler hakkında özensiz düşünmeyi teşvik eder ve aşırı çalışma süresi kontrolüne yol açar.

  • Evrensel bir temel sınıf kullanmak maliyet anlamına gelir: Nesnelerin polimorfik olması için yığın olarak tahsis edilmesi gerekir; bu bellek ve erişim maliyetini ifade eder. Yığın nesneleri doğal olarak kopyalama anlamını desteklemez. Öbek nesneleri, basit kapsamlı davranışı desteklemez (bu, kaynak yönetimini karmaşıklaştırır). Evrensel bir temel sınıf, dynamic_cast'in ve diğer çalışma zamanı denetimlerinin kullanılmasını teşvik eder.


83
Hiçbir / temel sınıf nesnesine ihtiyacımız yok / hiç / düşünce kontrolüne ihtiyacımız yok ...;)
Piskvor,

3
@Piskvor - LOL Müzik videosunu görmek istiyorum!
Byron Whitlock

11
Objects must be heap-allocated to be polymorphic- Bu ifadenin genellikle doğru olduğunu düşünmüyorum. Yığın üzerinde kesinlikle bir polimorfik sınıf örneği oluşturabilir ve onu temel sınıflarından birine bir işaretçi olarak geçirerek polimorfik davranış elde edebilirsiniz.
Byron

5
Java'da, miras almadığı zamana Foobir referansa referans atmaya çalışmak , deterministik olarak bir istisna atar. C ++ 'da, bir işaretçiyi Foo'ya bir işaretçi çubuğuna atmaya çalışmak, Sarah Connor'ı öldürmek için zamanında bir robot gönderebilir. BarBarFoo
supercat

3
@supercat Bu yüzden dynamic_cast <> kullanıyoruz. SkyNet'ten ve gizemli bir şekilde görünen Chesterfield kanepelerinden kaçınmak için.
Captain Giraffe

44

İlk önce neden bir temel sınıfa sahip olmak isteyeceğinizi düşünelim. Birkaç farklı neden düşünebilirim:

  1. Her türden nesne üzerinde çalışacak genel işlemleri veya koleksiyonları desteklemek için.
  2. Tüm nesnelerde ortak olan çeşitli prosedürleri dahil etmek (bellek yönetimi gibi).
  3. Her şey bir nesnedir (ilkel değil!). Bazı dillerde (Objective-C gibi) buna sahip değildir, bu da işleri oldukça karmaşık hale getirir.

Smalltalk, Ruby ve Objective-C markasının dillerinin temel sınıflara sahip olmasının iki iyi nedeni bunlardır (teknik olarak Objective-C gerçekten bir temel sınıfa sahip değildir, ancak tüm niyet ve amaçlar için vardır).

# 1 için, tüm nesneleri tek bir arabirim altında birleştiren bir temel sınıfa duyulan ihtiyaç, şablonların C ++ 'ya dahil edilmesiyle ortadan kaldırılır. Örneğin:

void somethingGeneric(Base);

Derived object;
somethingGeneric(object);

parametrik polimorfizm aracılığıyla yazım bütünlüğünü baştan sona koruyabildiğiniz zaman gereksizdir!

template <class T>
void somethingGeneric(T);

Derived object;
somethingGeneric(object);

# 2 için, Objective-C'de bellek yönetimi prosedürleri bir sınıfın uygulamasının bir parçasıdır ve temel sınıftan miras alınır, C ++ 'da bellek yönetimi, miras yerine kompozisyon kullanılarak gerçekleştirilir. Örneğin, herhangi bir türdeki nesneler üzerinde referans sayımı gerçekleştirecek bir akıllı işaretçi sarıcı tanımlayabilirsiniz:

template <class T>
struct refcounted
{
  refcounted(T* object) : _object(object), _count(0) {}

  T* operator->() { return _object; }
  operator T*() { return _object; }

  void retain() { ++_count; }

  void release()
  {
    if (--_count == 0) { delete _object; }
  }

  private:
    T* _object;
    int _count;
};

Ardından, nesnenin kendisinde yöntemler çağırmak yerine, sarmalayıcısında yöntemleri çağırırsınız. Bu sadece daha genel programlamaya izin vermekle kalmaz, aynı zamanda endişeleri ayırmanıza da olanak tanır (çünkü ideal olarak, nesneniz farklı durumlarda belleğinin nasıl yönetilmesi gerektiğinden daha çok ne yapması gerektiği ile ilgilenmelidir).

Son olarak, hem ilkellere hem de C ++ gibi gerçek nesnelere sahip bir dilde, bir temel sınıfa ( her değer için tutarlı bir arabirim ) sahip olmanın faydaları kaybolur, çünkü o zaman bu arabirime uymayan belirli değerlere sahip olursunuz. İlkelleri bu tür bir durumda kullanmak için, onları nesnelere kaldırmanız gerekir (eğer derleyiciniz bunu otomatik olarak yapmazsa). Bu çok fazla karmaşıklık yaratır.

Öyleyse, sorunuzun kısa cevabı: C ++ 'nın bir temel sınıfı yoktur çünkü şablonlar aracılığıyla parametrik polimorfizm olması gerekmez.


Tamam! Faydalı bulduğunuza sevindim :)
Jonathan Sterling

2
3. nokta için, C # ile karşılık veriyorum. C # temelögeye sahiptir olabilir için kutulu object( System.Object), ancak böyle bir şey gerekmez. Derleyiciye intve System.Int32takma adlardır ve birbirlerinin yerine kullanılabilir; çalışma zamanı gerektiğinde boksla ilgilenir.
Cole Johnson

C ++ 'da boks yapmaya gerek yoktur. Şablonlarla derleyici, derleme zamanında doğru kodu üretir.
Rob K

2
Lütfen bu (eski) yanıt bir referans sayılan akıllı işaretçi gösterirken, std::shared_ptrbunun yerine C ++ 11'in artık kullanılması gerektiğini unutmayın.

15

C ++ değişkenleri için baskın paradigma değere göre geçirmedir, ref-by-ref değil. Her şeyi bir kökten türetilmeye zorlamak, Objectonları değere göre geçirmeyi bir hata haline getirir.

(Çünkü bir Nesneyi değere göre parametre olarak kabul etmek, tanımı gereği onu dilimlere ve ruhunu kaldırır).

Bu hoş değil. C ++, size bir seçenek sunarak, değer mi yoksa referans anlambilim mi istediğinizi düşünmenizi sağlar. Bu, performans hesaplamasında önemli bir şeydir.


1
Kendi başına bir hata olmaz. Tür hiyerarşileri zaten mevcuttur ve uygun olduğunda değere göre geçişi oldukça iyi kullanırlar. Bununla birlikte, referansa göre geçişin daha uygun olduğu yerlerde daha fazla yığın tabanlı stratejileri teşvik edecektir.
Dennis Zickefoose

IMHO, iyi bir dil, türetilmiş bir sınıfın referansa göre dilim kullanarak meşru bir şekilde temel sınıf nesnesi olarak kullanılabildiği, dilim-değer kullanılarak birine dönüştürülebildiği durumlar için farklı miras türlerine sahip olmalıdır. ve meşru olmayanlar. Dilimlenebilen şeyler için ortak bir temel türe sahip olmanın değeri sınırlı olacaktır, ancak birçok şey dilimlenemez ve dolaylı olarak depolanması gerekir; Bu tür şeylerin bir ortaktan kaynaklanmasının ek maliyeti Objectnispeten düşük olacaktır.
supercat

5

Sorun şu ki, C ++ 'da böyle bir tip var! Öyle void. :-) Herhangi bir işaretçi, void *temel türlere işaretçiler, sanal tablo içermeyen sınıflar ve sanal tablo içeren sınıflar da dahil olmak üzere güvenli bir şekilde dolaylı olarak kullanılabilir .

Tüm bu nesne kategorileriyle uyumlu olması gerektiğinden, voidkendisi sanal yöntemler içeremez. Sanal işlevler ve RTTI olmadan, tür hakkında hiçbir yararlı bilgi elde edilemez void(HER türle eşleşir, bu nedenle yalnızca HER tür için doğru olan şeyleri söyleyebilir), ancak sanal işlevler ve RTTI, basit türleri çok etkisiz hale getirir ve C ++ 'nın var olmasını engeller. doğrudan bellek erişimi vb. ile düşük seviyeli programlama için uygun bir dil

Yani böyle bir tip var. Dilin düşük seviyeli doğası nedeniyle çok minimalist (aslında boş) bir arayüz sağlar. :-)


İşaretçi türleri ve nesne türleri aynı değildir. Bir tür nesneye sahip olamazsınız void.
Kevin Panko

1
Aslında, C ++ 'da kalıtım genellikle böyle çalışır. Yaptığı en önemli şeylerden biri, işaretçi ve başvuru türlerinin uyumluluğudur: bir sınıfa bir işaretçi gerektiğinde, türetilmiş bir sınıfa bir işaretçi verilebilir. Ve iki tür arasında durağan_çevrim yapabilme yeteneği, bir hiyerarşide bağlı olduklarının bir işaretidir. Bu bakış açısından, void kesinlikle C ++ 'da evrensel bir temel sınıftır.
Ellioh

Bir tür değişkeni voidbildirirseniz, derlenmez ve bu hatayı alırsınız . Java'da bir tür değişkeniniz olabilir Objectve bu işe yarar. Bu voidve "gerçek" tip arasındaki fark budur . Belki de voidher şey için temel tür olduğu doğrudur , ancak herhangi bir kurucu, yöntem veya alan sağlamadığı için, orada olup olmadığını söylemenin bir yolu yoktur. Bu iddia kanıtlanamaz veya reddedilemez.
Kevin Panko

1
Java'da her değişken bir referanstır. Fark bu. :-) (BTW, C ++ 'da bir değişken bildirmek için kullanamayacağınız soyut sınıflar da vardır). Ve cevabımda RTTI'yi neden boşluktan almanın mümkün olmadığını açıkladım. Yani teorik olarak boşluk hala her şey için temel bir sınıftır. static_cast yalnızca ilgili türler arasında yayınlar gerçekleştirdiğinden ve void için kullanılabildiğinden bir kanıttır. Ancak haklısınız, boşlukta RTTI erişiminin olmaması, daha yüksek seviyeli dillerdeki temel türlerden gerçekten çok farklıdır.
Ellioh

1
@Ellioh C ++ 11 standardı §3.9.1p9'a danıştıktan sonra, voidbunun bir tür olduğunu kabul ediyorum (ve biraz akıllıca bir kullanım? : keşfettim ), ancak yine de evrensel bir temel sınıf olmaktan çok uzak. Birincisi, "tamamlanamayan tamamlanmamış bir tür" ve diğeri için void&tür yok .
bcrist

-2

C ++ güçlü bir şekilde yazılmış bir dildir. Yine de şablon uzmanlaşması bağlamında evrensel bir nesne türüne sahip olmaması şaşırtıcı.

Örneğin, kalıbı ele alalım

template <class T> class Hook;
template <class ReturnType, class ... ArgTypes>
class Hook<ReturnType (ArgTypes...)>
{
   ...
   ReturnType operator () (ArgTypes... args) { ... }
};

hangi şekilde örneklenebilir

Hook<decltype(some_function)> ...;

Şimdi, belirli bir fonksiyon için aynısını istediğimizi varsayalım. Sevmek

template <auto fallback> class Hook;
template <auto fallback, class ReturnType, class ... ArgTypes>
class Hook<ReturnType fallback(ArgTypes...)>
{
   ...
   ReturnType operator () (ArgTypes... args) { ... }
};

özel örnekleme ile

Hook<some_function> ...

Ancak ne yazık ki, T sınıfı uzmanlaşmadan önce herhangi bir tür için (sınıf olsun ya da olmasın) auto fallbackayakta durabilse de, herhangi bir tür uzmanlaşmadan önce tür olmayan şablon argümanı.

Bu nedenle, genel olarak bu kalıp, tür şablonu bağımsız değişkenlerinden tür olmayan şablon bağımsız değişkenlerine aktarılmaz.

C ++ dilindeki birçok köşede olduğu gibi, cevap muhtemelen "hiçbir komite üyesi bunu düşünmedi".


(Gibi bir şey) ReturnType fallback(ArgTypes...)işe yarasa bile , kötü bir tasarım olur. template <class T, auto fallback> class Hook; template <class ReturnType, class ... ArgTypes> class Hook<ReturnType (ArgTypes...), ReturnType(*fallback)(ArgTypes...)> ...istediğinizi yapar ve şablon parametrelerinin Hooktutarlı türde
Caleth

-4

C ++ başlangıçta "sınıflarla C" olarak adlandırıldı. C # gibi daha modern bazı şeylerin aksine, C dilinin bir ilerlemesidir. Ve C ++ 'yı bir dil olarak değil, dillerin temeli olarak göremezsiniz (Evet, Scott Meyers'ın Etkili C ++ kitabını hatırlıyorum).

C'nin kendisi, dillerin, C programlama dilinin ve ön işlemcisinin bir karışımıdır.

C ++ başka bir karışım ekler:

  • sınıf / nesneler yaklaşımı

  • şablonlar

  • STL

Ben şahsen doğrudan C'den C ++ 'ya gelen bazı şeyleri sevmiyorum. Bir örnek, enum özelliğidir. C # 'nin geliştiricinin kullanmasına izin verme şekli çok daha iyidir: numaralandırmayı kendi kapsamında sınırlar, bir Count özelliğine sahiptir ve kolayca yinelenebilir.

C ++, C ile geriye dönük uyumlu olmak istediğinden, tasarımcı C dilinin bütünüyle C ++ 'ya girmesine izin verdi (bazı ince farklılıklar var, ancak bir C derleyicisini kullanarak yapabileceğiniz hiçbir şeyi hatırlamıyorum. bir C ++ derleyicisi kullanamadı).


"C ++ 'yı bir dil olarak değil, dillerin temeli olarak göremiyorum" ... bu tuhaf ifadenin neye yol açtığını gerçekten açıklamaya ve göstermeye özen gösterin? Çoklu paradigmalar "çoklu dillerden" farklıdır, ancak önişlemcinin derleyici adımlarının geri kalanından ayrı olduğunu söylemek doğru olur - iyi bir nokta. Yeniden numaralandırmalar - istenirse önemsiz bir şekilde bir sınıf kapsamına sarılabilirler ve tüm dil iç gözlemden yoksundur - büyük bir sorun, ancak bu durumda yaklaştırılabilir - Boost kasasının benum koduna bakın. Demek istediğin sadece Q mu: C ++ evrensel bir temelle başlamadı mı?
Tony Delroy

@Tony: Merakla, başka bir dil olarak bahsetmedikleri bile bahsetmedikleri tek kitap önişlemci, ki bu da sizin ayrı ayrı düşündüğünüz tek kitap. Önişlemci önce kendi girdisini ayrıştırırken bakış açınızı anlıyorum. Ancak, bir şablon oluşturma mekanizmasına sahip olmak, genellemeyi sizin için yapan başka bir dile sahip olmak gibidir. Bir şablon sözdizimi uygularsınız ve sahip olduğunuz her tür için derleyici oluşturan işlevlere sahip olursunuz. C ile yazılmış bir programa sahip olduğunuzda ve girdisini bir C ++ derleyicisine verebildiğinizde, bu iki dildir: C ve C nesnelerle => C ++
sergiol

STL, bir eklenti olarak görülebilir, ancak şablonlar aracılığıyla C ++ 'nın gücünü DOĞAL OLARAK genişleten çok pratik sınıflara ve kapsayıcılara sahiptir. Ve söylediğim numaralar NATIVE numaralandırmalarıdır. Boost'tan bahsettiğinizde, dile NATIVE olmayan üçüncü taraf koduna güveniyorsunuz. C # 'da, yinelenebilir ve öz kapsamlı bir numaralandırmaya sahip olmak için harici hiçbir şey eklemem gerekmiyor. C ++ 'da sıklıkla kullandığım bir numara, bir numaralamanın son öğesini - atanmış değerler olmadan, böylece otomatik olarak sıfırdan başlatılır ve artırılır - NUM_ITEMS gibi bir şey olarak çağırmaktır ve bu bana üst sınırı verir.
sergiol

2
daha fazla açıklama için teşekkürler - En azından nereden geldiğini görebiliyorum; Birkaç kişinin, şablonları kullanmayı öğrenmenin diğer C ++ programlamasından ayrıldığını düşündüğünden şikayet ettiğini duydum, ancak bu kesinlikle benim deneyimim değil. Diğer okuyuculardan, sunduğunuz diğer perspektiflerden istediklerini yapmaları için ayrılacağım. Şerefe.
Tony Delroy

1
Bence evrensel bir temel türe sahip olmanın en büyük sorunu, böyle bir türün herhangi bir sanal yöntemi olmasaydı işe yaramaz olmasıdır; C ++, herhangi bir sanal yöntemi olmayan yapı türleri için herhangi bir ek yük eklemek istemedi, ancak herhangi bir sanal yönteme sahip herhangi bir türdeki her nesnenin, en azından, bir gönderim tablosuna işaretçi olması gerekir. Sınıflar ve yapılar farklı evrenlerde yaşamış olsaydı, evrensel bir sınıf nesnesine sahip olmak mümkün olabilirdi, ancak sınıflar ve yapılar temelde C ++ 'da aynı şeydir.
supercat
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.