C ++ neden bir kurucunun adresini almanıza izin vermiyor?


14

Bunun dili kavramsal olarak kırmasının belirli bir nedeni veya bazı durumlarda bunun teknik olarak mümkün olmaması için belirli bir neden var mı?

Kullanım yeni operatör ile olacaktır.

Düzenleme: Benim "yeni operatör" ve "operatör yeni" düz almak ve doğrudan olmak umut vereceğim.

Sorunun amacı şudur: inşaatçılar neden özeldir ? Elbette, dil özelliklerinin bize neyin yasal olduğunu, ancak mutlaka ahlaki olmadığını söylediğini unutmayın. Yasal olan şey tipik olarak dilin geri kalanıyla mantıksal olarak tutarlı olan, basit ve özlü olan ve derleyicilerin uygulaması için neyin mümkün olduğu konusunda bilgilendirilir. Standartlar komitesinin bu faktörlerin tartımındaki muhtemel mantığı kasıtlı ve ilginçtir - dolayısıyla soru.


Bir kurucunun adresini ele almak değil, tipin etrafından geçebilmek problem olacaktır. Şablonlar bunu yapabilir.
Euphoric

İşlevin bağımsız değişkeni olarak belirtilecek bir yapıcı kullanarak nesne oluşturmak istediğiniz bir işlev şablonunuz varsa ne olur?
Praxeolitic

1
Düşünebileceğim herhangi bir örnek için alternatifler olacak, ama yine de inşaatçılar neden özel olmalı? Çoğu programlama dilinde kullanmayacağınız birçok şey vardır, ancak bunun gibi özel durumlar genellikle bir gerekçe ile gelir.
Praxeolitic

1
@RobertHarvey Bir fabrika sınıfı yazmak üzereyken soru bana geldi.
Praxeolitic

1
Acaba C ++ 11 std::make_uniqueve std::make_sharedbu sorunun altında yatan pratik motivasyonu yeterince çözüp çözemeyeceğini merak ediyorum . Bunlar şablon metodlarıdır, yani girdi argümanlarını kurucuya yakalamak ve daha sonra bunları gerçek kurucuya iletmek gerekir.
rwong

Yanıtlar:


10

İşaretçiden üyeye işlevler yalnızca aynı imzayla birden fazla üye işleviniz varsa mantıklıdır; aksi takdirde işaretçiniz için yalnızca bir olası değer olacaktır. Ancak bu yapıcılar için mümkün değildir, çünkü C ++ 'da aynı sınıfın farklı kurucularının farklı imzaları olmalıdır.

Stroustrup için alternatif, C ++ için bir sözdizimi seçmek olurdu, burada kurucular sınıf adından farklı bir ada sahip olabilirlerdi - ancak bu, mevcut ctor sözdiziminin çok zarif yönlerini önleyecek ve dili daha karmaşık hale getirecekti. Benim için sadece bir nesnenin ctor'dan farklı bir initişleve (işaretçi-üyeler olabileceği normal bir üye işlevi) başlatılmasıyla "dış kaynak kullanımı" ile kolayca simüle edilebilen nadiren ihtiyaç duyulan bir özelliğe izin vermek için yüksek bir fiyat gibi görünüyor . oluşturulan).


2
Yine de neden engellemelisiniz memcpy(buffer, (&std::string)(int, char), size)? (Muhtemelen son derece
bozucu

3
üzgünüm, ama yazdıklarının anlamı yok. Bir kurucu işaret eden üyeye işaretçi ile yanlış bir şey görmüyorum. Ayrıca, kaynak bağlantısı olmadan bir şey alıntı gibi geliyor.
BЈовић

1
@ThomasEding: Bu ifadenin tam olarak ne yapmasını bekliyorsunuz? String ctor'un montaj kodunun kopyalanması biraz mı? "Boyut" nasıl belirlenir (standart üye işlevi için eşdeğer bir şey deneseniz bile)?
Doc Brown

Ben bir serbest fonksiyon işaretçisinin adresi verildiği gibi aynı şeyi yapmak beklenir memcpy(buffer, strlen, size). Muhtemelen montajı kopyalardı, ama kim bilir. Kodun kilitlenmeden çağrılabilip çağrılamayacağı, kullandığınız derleyici hakkında bilgi gerektirir. Aynı şey boyutu belirlemek için de geçerlidir. Yüksek düzeyde platforma bağımlı olacaktır, ancak üretim kodunda taşınabilir olmayan birçok C ++ yapısı kullanılır. Yasadışı ilan etmek için hiçbir neden göremiyorum.
Thomas Eding

@ThomasEding: Uyumlu bir C ++ derleyicisinin, bir veri işaretçisiymiş gibi bir işlev işaretçisine erişmeye çalışırken tanılama yapması beklenir. Uygun olmayan bir C ++ derleyicisi her şeyi yapabilir, ancak aynı zamanda bir kurucuya erişmek için c ++ olmayan bir yol da sağlayabilir. Bu C ++ 'a uygun kod kullanmanın bir özelliği eklemek için bir neden değildir.
Bart van Ingen Schenau

5

Bir yapıcı, nesne henüz mevcut olmadığında çağırdığınız bir işlevdir, bu nedenle üye işlevi olamaz. Statik olabilir.

Bir yapıcı, bellek tahsis edildikten sonra ancak tamamen başlatılmadan önce bu işaretçi ile çağrılır. Sonuç olarak bir kurucu bir dizi ayrıcalıklı özelliğe sahiptir.

Bir kurucuya bir işaretçi olsaydı, ya statik bir işaretçi, fabrika işlevi gibi bir şey ya da bellek ayırmadan hemen sonra çağrılacak bir şey için özel bir işaretçi olurdu. Sıradan bir üye işlevi olamaz ve hala bir kurucu olarak çalışamaz.

Akla gelen tek yararlı amaç, yeni operatöre, hangi kurucunun kullanılacağı konusunda dolaylı olarak izin vermesi için geçirilebilen özel bir işaretçi türüdür. Bu kullanışlı olabilir, ancak önemli yeni sözdizimi gerektirir ve muhtemelen cevap: onlar hakkında düşündüm ve çabaya değmezdi.

Sadece ortak başlatma kodunu yeniden düzenlemek istiyorsanız, sıradan bir hafıza fonksiyonu genellikle yeterli bir cevaptır ve bunlardan birine bir işaretçi alabilirsiniz.


Bu en doğru cevap gibi görünüyor. Birçok (yıl) yıl önce yeni operatör ve “yeni operatör” ün dahili çalışmaları ile ilgili bir makaleyi hatırlıyorum. operatör new () alan ayırır. Yeni operatör, bu tahsis edilen alanla yapıcıyı çağırır. Bir kurucunun adresini almak “özel” dir çünkü kurucuyu çağırmak alan gerektirir. Böyle bir kurucu çağırmak için erişim yeni yerleşimdir.
Bill Door

1
"Var" sözcüğü, bir nesnenin bir adrese sahip olabileceği ve bellek ayırabildiği ancak başlatılmayacağı ayrıntıyı gizler. Üye işlevi olsun ya da olmasın, bu işaretçi elde bir işlev bir işlev yapar çünkü bir nesne örneği ile açıkça (başlatılmamış olsa bile) ilişkili düşünüyorum. Bununla birlikte, cevap iyi bir noktaya işaret eder: yapıcı, başlatılmamış bir nesne üzerinde çağrılabilen tek üye işlevidir.
Praxeolitic

Boşver, görünüşe göre "özel üye işlevleri" olarak adlandırılmışlardır. C ++ 11 standardının 12. maddesi: "Varsayılan yapıcı (12.1), kopya oluşturucu ve kopya atama işleci (12.8), taşıma yapıcısı ve taşıma atama işleci (12.8) ve yıkıcı (12.4) özel üye işlevleridir ."
Praxeolitic

Ve 12.1: "Bir kurucu sanal (10.3) veya statik (9.4) olamaz ." (Vurgu)
Praxeolitic

1
Gerçek şu ki, hata ayıklama sembolleri ile derlerseniz ve bir yığın izlemesi ararsanız, aslında yapıcıya bir işaretçi vardır. Asla başaramadığım şey, bu işaretçiyi elde etmek için sözdizimini bulmaktır ( &A::Adenediğim derleyicilerin hiçbirinde çalışmaz.)
alfC

-3

Bunun nedeni, bunların dönüş türü bir kurucu olmaması ve bellekteki kurucu için herhangi bir yer ayırmamanızdır. Beyan sırasında değişken olması durumunda yaptığınız gibi. Örneğin: u basit değişken X yazarsanız Derleyici bunun anlamını anlamayacağından derleyici hata üretir. Ancak Int x yazdığınızda; Sonra derleyici int veri değişkeni türünü bilmek gelir, bu yüzden değişken için biraz yer ayırır.

Sonuç: - Sonuç olarak, geri dönüş tipinin hariç tutulması nedeniyle adresin hafızaya alınmayacağı sonucuna varılmıştır.


1
Yapıcıdaki kodun bir yerde olması gerektiğinden bellekte bir adres olması gerekir. Yığın üzerinde yer ayırmaya gerek yoktur, ancak bellekte bir yerde olmalıdır. Sen edebilirsiniz değerlerini döndürmez fonksiyonların adresini alır. (void)(*fptr)()dönüş değeri olmayan bir işleve bir işaretçi bildirir.
Praxeolitic

2
Sorunun noktasını kaçırdınız - orijinal gönderi, kurucunun sağladığı sonucu değil, kurucunun kodunun adresini almayı sordu. Ayrıca, bu tahtada, lütfen tam kelimeleri kullanın: "u", "siz" için kabul edilebilir bir alternatif değildir.
BobDalgleish

Praxeolitic, sanırım biz herhangi bir dönüş türünden bahsetmezsek derleyici ctor için belirli bir bellek konumu ayarlamaz ve konumu dahili olarak ayarlanır .... c tarafından verilen herhangi bir şeyin adresini getirebilir miyiz? derleyici? Eğer yanılıyorsam, lütfen doğru cevapla beni düzeltin
Goyal

Ve bana referans değişkeni de anlat. Referans değişkenin adresini getirebilir miyiz? Hayır ise, hangi adres printf ("% u", & (& (j))); & j = x ise x = 10 ise yazdırılıyor mu? Printf ile yazdırılan adres ve x adresi aynı olmadığından
Goyal

-4

Vahşi bir tahmin yapacağım:

C ++ yapıcısı ve yıkıcısı işlev değildir: makrolardır. Nesnenin oluşturulduğu kapsama ve nesnenin yok edildiği kapsama girerler. Buna karşılık, hiçbir kurucu ya da yıkıcı yoktur, nesne sadece IS'dir.

Aslında, sınıftaki diğer işlevlerin işlevler olmadığını düşünüyorum, ancak adresleri aldığınız için DONT'un satır içi işlevlerini satır içi işlevler aldınız (derleyici, bunun üzerine geldiğinizi fark eder ve kodu satır içine almaz veya işlevin içine yerleştirmez ve bu işlevi optimize eder) ve bunun yerine adres almamış olsanız bile işlev "hala orada" gibi görünür.

C ++ "nesnesinin" sanal tablosu, bir JavaScript nesnesi gibi değildir, burada onun yapıcısını alabilir ve çalışma zamanında ondan nesne oluşturabilirsiniz new XMLHttpRequest.constructor, bunun yerine bu nesneyle arabirim aracı olarak işlev gören anonim işlevlere işaretçiler koleksiyonu , nesne oluşturma yeteneği hariç. Ve nesneyi "silmek" bile mantıklı değil, çünkü bir yapıyı silmeye çalışmak gibi, yapamazsınız: bu sadece bir yığın etiketi, sadece başka bir etiketin altında istediğiniz gibi yazabilirsiniz: 4 tamsayı olarak bir sınıf kullanın:

/* i imagine this string gets compiled into a struct, one of which's members happens to be a const char * which is initialized to exactly your string: no function calls are made during construction. */
std::string a = "hello, world";
int *myInt = (int *)(*((void **)&a));
myInt[0] = 3;
myInt[1] = 9;
myInt[2] = 20;
myInt[3] = 300;

Bellek sızıntısı yok, sorun yok, ancak nesne arabirimi ve dize için ayrılmış bir yığın yığın alanını etkili bir şekilde boşa harcamanız dışında, ancak programınızı yok etmeyecek (kullanmaya çalışmadığınız sürece) bir daha dize olarak).

Aslında, daha önceki varsayımlarım doğruysa: dizenin tam maliyeti sadece bu 32 baytı ve sabit dize alanını depolama maliyetidir: işlevler sadece derleme zamanında kullanılır ve sonra da satır içine alınabilir ve atılabilir nesne oluşturulur ve kullanılır (Bir yapı ile çalışıyor ve yalnızca herhangi bir işlev çağrısı olmadan doğrudan başvuruyorsunuz gibi, işlev atlamaları yerine yinelenen çağrılar olduğundan emin olun, ancak bu genellikle daha hızlıdır ve daha az alan kullanır). Özünde, herhangi bir işlevi çağırdığınızda, derleyici, dil tasarımcılarının belirlediği istisnalar dışında, bu çağrıyı kelimenin tam anlamıyla yapma talimatlarıyla değiştirir.

Özet: C ++ nesnelerinin ne oldukları hakkında hiçbir fikri yoktur; onlarla arabirim oluşturmak için tüm araçlar statik olarak satır içine alınır ve çalışma zamanında kaybolur. Bu, sınıflarla çalışmayı yapıları verilerle doldurmak kadar verimli hale getirir ve herhangi bir işlev çağırmadan bu verilerle doğrudan çalışır (bu işlevler satır içine alınır).

Bu, COM / ObjectiveC'nin yanı sıra tür bilgilerini dinamik olarak, çalışma zamanı yükü, bellek yönetimi, inşaat çağrıları pahasına, derleyici bu bilgileri atmayacağı için javascript yaklaşımlarından tamamen farklıdır: dinamik dağıtım için. Bu da bize programımızda çalışma zamanında "Konuşma" yapma ve yansıtılabilir bileşenlere sahip olarak çalışırken geliştirme becerisi kazandırır.


2
üzgünüm, ama bu "cevabın" bazı kısımları ya yanlış ya da tehlikeli şekilde yanıltıcıdır. Ne yazık ki, yorum alanı hepsini listelemek için çok küçük (çoğu yöntem satır içine alınmayacak, bu sanal dağıtımı engelleyecek ve ikiliyi şişirecektir; satır içi olsa bile, erişilebilir bir yerde adreslenebilir bir kopya olabilir; alakasız kod örneği en kötü durum yığınızı bozar ve en iyi durumda varsayımlarınıza uymaz; ...)
hoffmale

Cevap naif, sadece yapıcı / yıkıcıya neden referans yapılamadığına dair tahminimi ifade etmek istedim. Sanal sınıflar söz konusu olduğunda, vtable'ın kalıcı olması ve adreslenebilir kodun vtable'a başvurabilmesi için bellekte olması gerektiğine katılıyorum. Ancak, sanal bir sınıf uygulamayan sınıflar, std :: string durumunda olduğu gibi satır içi gibi görünmektedir. Her şey satır içine alınmaz, ancak bellekte bir yerde minimal olarak "anonim" kod bloğuna konulmamış gibi görünen şeyler. Ayrıca, kod yığını nasıl bozar? Elbette ipi kaybettik, aksi takdirde yaptığımız tek şey yeniden yorumlamak.
Dmitry

Bellek yerinin içeriği istemeden değiştirildiğinde, bilgisayar programında bellek bozulması oluşuyor. Bu program kasıtlı olarak yapar ve artık bu dizeyi kullanmaya çalışmaz, bu nedenle bozulma yoktur, yığın alanı boşa harcar. Ancak evet, dizenin değişmezi artık korunmaz, kapsamı karmaşıklaştırır (sonunda yığın kurtarılır).
Dmitry

string uygulamasına bağlı olarak istemediğiniz baytların üzerine yazabilirsiniz. Dize struct { int size; const char * data; };(varsaydığınız gibi) bir şeyse, bir x86 makinesinde yalnızca 8 bayt ayırdığınız bir bellek adresine 4 * 4 Bayt = 16 bayt yazarsınız, böylece diğer verilerin üzerine 8 bayt yazılır (bu, yığınızı bozabilir) ). Neyse ki, std::stringnormalde kısa dizeler için bazı yerinde optimizasyonları vardır, bu nedenle bazı büyük std uygulamalarını kullanırken örneğiniz için yeterince büyük olmalıdır.
hoffmale

@hoffmale kesinlikle haklısın, 8 bayt, hatta 8 bayt olabilir. Bununla birlikte, dizenin boyutunu öğrendikten sonra, belleğin geçerli kapsamda yığınta olduğunu da bilirsiniz ve istediğiniz gibi kullanabilirsiniz. Demek istediğim, yapıyı biliyorsanız, sınıflarını IUnknown's vtable'ın bir parçası olarak tanımlayan bir uuid'e sahip COM nesnelerinin aksine, sınıfla ilgili herhangi bir bilgiden bağımsız bir şekilde paketlenmiştir. Derleyici, bu verilere doğrudan satır içi veya karıştırılmış statik işlevler aracılığıyla erişir.
Dmitry
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.