Bir T * neden kayıt defterine geçirilebilir, ancak unique_ptr <T> yapamaz?


85

Chandler Carruth'un CppCon 2019'daki konuşmasını izliyorum:

Sıfır Maliyetli Soyutlama Yok

İçinde, bir std::unique_ptr<int>over a kullanarak ne kadar ek yüke maruz kaldığınıza nasıl şaşırdığını gösteren bir örnek verir int*; bu segment 17:25 zaman noktasında başlar.

Örnek snippet çiftinin (godbolt.org) derleme sonuçlarına bir göz atabilirsiniz - gerçekten de, derleyicinin unique_ptr değerini geçmeye istekli görünmediğine - aslında alt satırda sadece bir adres - bir kaydın içinde, sadece düz bellekte.

Bay Carruth'un saat 27: 00'da yaptığı noktalardan biri, C ++ ABI'nin bellekte geçirilmesi için değer değeri parametrelerine (bazıları değil hepsi; belki de ilkel olmayan tipler - önemsizce inşa edilemeyen tipler?) İhtiyaç duymasıdır. bir kayıt içinde değil.

Sorularım:

  1. Bu aslında bazı platformlarda ABI gereksinimi midir? (hangisi?) Ya da belki belirli senaryolarda biraz kötümserlik?
  2. ABI neden böyle? Yani, bir yapı / sınıfın alanları kayıtlara, hatta tek bir kayıta sığarsa - neden bu kayıt içine geçiremeyelim?
  3. C ++ standartlar komitesi bu noktayı son yıllarda veya hiç tartıştı mı?

Not - Bu soruyu kod olmadan bırakmamak için:

Düz işaretçi:

void bar(int* ptr) noexcept;
void baz(int* ptr) noexcept;

void foo(int* ptr) noexcept {
    if (*ptr > 42) {
        bar(ptr); 
        *ptr = 42; 
    }
    baz(ptr);
}

Benzersiz işaretçi:

using std::unique_ptr;
void bar(int* ptr) noexcept;
void baz(unique_ptr<int> ptr) noexcept;

void foo(unique_ptr<int> ptr) noexcept {
    if (*ptr > 42) { 
        bar(ptr.get());
        *ptr = 42; 
    }
    baz(std::move(ptr));
}

8
Eminim ABI gereksinimi tam olarak ne olduğunu değilim ama kayıtlarda yapılar koyarak yasağı yok
Harold

6
Tahmin etmeliydim this, bunun geçerli bir yere işaret eden bir işaretçi gerektiren önemsiz üye işlevlerle ilgisi olduğunu söyleyebilirim . unique_ptrbunlara sahip. Kaydın bu amaçla dökülmesi, tüm "bir kayıtta geçiş" optimizasyonunu olumsuz yönde etkileyecektir.
StoryTeller - Unslander Monica

2
itanium-cxx-abi.github.io/cxx-abi/abi.html#calls . Yani bu davranış gerekli. Neden? itanium-cxx-abi.github.io/cxx-abi/cxx-closed.html , C-7 sorununu arayın. Orada bir açıklama var, ama çok ayrıntılı değil. Ama evet, bu davranış bana mantıklı gelmiyor. Bu nesneler normalde yığıntan geçirilebilir. Onları yığmak için itmek ve daha sonra referansı (sadece "önemsiz" nesneler için) geçirmek israf gibi görünüyor.
geza

6
Görünüşe göre C ++ kendi prensiplerini ihlal ediyor, ki bu oldukça üzücü. Ben% 140 herhangi bir unique_ptr derleme sonra sadece yok olduğuna ikna oldu. Sonuçta sadece derleme zamanında bilinen ertelenmiş bir yıkıcı çağrısı.
One Man Monkey Kadrosu

7
@MaximEgorushkin: El ile yazmış olsaydınız, işaretçiyi yığına değil bir kayıt defterine koyardınız.
einpoklum

Yanıtlar:


49
  1. Bu aslında bir ABI gereksinimi mi, yoksa belirli senaryolarda biraz kötümser mi?

Örnek olarak System V Application Binary Interface AMD64 Mimari İşlemci Desteği verilebilir . Bu ABI, 64 bit x86 uyumlu CPU'lar içindir (Linux x86_64 architecure). Bunu Solaris, Linux, FreeBSD, macOS, Linux için Windows Alt Sistemi izliyor:

Bir C ++ nesnesinin önemsiz olmayan bir kopya oluşturucu veya önemsiz olmayan bir yıkıcı varsa, görünmez başvuru tarafından geçirilir (nesne parametre listesinde INTEGER sınıfına sahip bir işaretçi ile değiştirilir).

Önemsiz bir kopya oluşturucuya veya önemsiz bir yıkıcıya sahip bir nesne değere göre iletilemez çünkü bu nesnelerin iyi tanımlanmış adresleri olmalıdır. Bir işlevden bir nesne döndürülürken de benzer sorunlar geçerlidir.

Önemsiz bir kopya oluşturucu ve önemsiz bir yıkıcı ile 1 nesneyi geçirmek için sadece 2 genel amaçlı kayıt kullanılabilir, yani sizeofkayıtlarda yalnızca 16'dan büyük olmayan nesnelerin değerleri geçirilebilir. Bkz Agner Fog tarafından kuralları çağrılması §7.1 özellikle çağıran sözleşmelerin detaylı tedavisi için Geçme ve nesneleri dönüyor. Kayıtlara SIMD tiplerini aktarmak için ayrı arama kuralları vardır.

Diğer CPU mimarileri için farklı ABI'ler vardır.


  1. ABI neden böyle? Yani, bir yapı / sınıfın alanları kayıtlara, hatta tek bir kayıta sığarsa - neden bu kayıt içine geçiremeyelim?

Bu bir uygulama ayrıntısıdır, ancak bir istisna işlendiğinde, yığın çözme sırasında, otomatik depolama süresine sahip olan nesneler yok edilen işlevler, yığınlar o zamana kadar gizlendiğinden, işlev yığını çerçevesine göre adreslenebilir olmalıdır. Yığın çözme kodu, yıkıcılarını çağırmak için nesnelerin adreslerine ihtiyaç duyar, ancak yazmaçlardaki nesnelerin bir adresi yoktur.

Bilgiçlikle, yıkıcılar nesneler üzerinde çalışır :

Bir nesne, inşaat süresi boyunca ([sınıf.cdtor]), kullanım ömrü boyunca ve yıkım döneminde bir depolama bölgesini işgal eder.

ve nesnenin kimliği onun adresi olduğu için adreslenebilir depolama alanı ayrılmazsa bir nesne C ++ 'da bulunamaz .

Kayıtlarda tutulan önemsiz bir kopya oluşturucuya sahip bir nesnenin adresi gerektiğinde, derleyici sadece nesneyi belleğe kaydedebilir ve adresi alabilir. Kopya oluşturucu önemsizse, diğer taraftan, derleyici sadece belleğe depolayamaz, bunun yerine bir referans alan ve dolayısıyla kayıtlardaki nesnenin adresini gerektiren kopya yapıcısını çağırmak gerekir. Çağıran kural, muhtemelen kopya oluşturucunun callee'de satır içi olup olmamasına bağlı olamaz.

Bunu düşünmenin bir başka yolu, önemsiz şekilde kopyalanabilen türler için, derleyicinin bir nesnenin değerini , gerektiğinde düz bellek depoları tarafından kurtarılabileceği kayıtlardaki aktarmasıdır . Örneğin:

void f(long*);
void g(long a) { f(&a); }

x86_64 üzerinde System V ABI ile:

g(long):                             // Argument a is in rdi.
        push    rax                  // Align stack, faster sub rsp, 8.
        mov     qword ptr [rsp], rdi // Store the value of a in rdi into the stack to create an object.
        mov     rdi, rsp             // Load the address of the object on the stack into rdi.
        call    f(long*)             // Call f with the address in rdi.
        pop     rax                  // Faster add rsp, 8.
        ret                          // The destructor of the stack object is trivial, no code to emit.

Düşünce uyandırıcı konuşmasında Chandler Carruth , şeyleri iyileştirebilecek yıkıcı hareketi uygulamak için (diğer şeylerin yanı sıra) kırıcı bir ABI değişikliğinin gerekli olabileceğinden bahsediyor . IMO, yeni ABI kullanan işlevler açıkça yeni bir farklı bağlantıya sahip olmayı seçerse, örneğin bunları extern "C++20" {}blok halinde bildirirse (muhtemelen, mevcut API'ları taşımak için yeni bir satır içi ad alanında) ABI değişikliği kırılmaz olabilir . Böylece yeni bağlantı ile sadece yeni işlev bildirimlerine karşı derlenen kod yeni ABI'yi kullanabilir.

ABI'nin, çağrılan işlev satır içine alındığında geçerli olmadığını unutmayın. Derleyici, bağlantı zamanı kod oluşturmanın yanı sıra, diğer çeviri birimlerinde tanımlanan işlevleri satır içine alabilir veya özel arama kuralları kullanabilir.


Yorumlar uzun tartışmalar için değildir; bu sohbet sohbete taşındı .
Samuel Liew

8

Yaygın ABI'lerle, önemsiz olmayan yıkıcı -> kayıtlara geçemez

(@ MaximEgorushkin'in cevabında bir yorumda @ harold'un örneğini kullanan bir noktanın bir örneği; @ Yakk'ın yorumuna göre düzeltildi.)

Derlerseniz:

struct Foo { int bar; };
Foo test(Foo byval) { return byval; }

olsun:

test(Foo):
        mov     eax, edi
        ret

yani Foonesne testbir register'a ( edi) iletilir ve ayrıca bir register'a ( eax) döndürülür .

Yıkıcı önemsiz olmadığında ( std::unique_ptrOP örnekleri gibi ) - Ortak ABI'lar yığına yerleştirilmesini gerektirir. Yıkıcı nesnenin adresini hiç kullanmasa bile bu doğrudur.

Böylece, derleme yapmazsanız, hiçbir şey yapmadan yıkıcı durumunda bile:

struct Foo2 {
    int bar;
    ~Foo2() {  }
};

Foo2 test(Foo2 byval) { return byval; }

olsun:

test(Foo2):
        mov     edx, DWORD PTR [rsi]
        mov     rax, rdi
        mov     DWORD PTR [rdi], edx
        ret

gereksiz yükleme ve depolama ile.


Bu argüman ile ikna olmadım. Önemsiz yıkıcı as-if kuralını yasaklamak için hiçbir şey yapmaz. Adrese uyulmazsa, kesinlikle bir adrese ihtiyaç duyulmasının bir nedeni yoktur. Bu nedenle, uygun bir derleyici, bunu gözlemlenebilir davranışı değiştirmezse, mutlu bir şekilde bir sicile koyabilir (ve mevcut derleyiciler aslında arayanlar bilinirse bunu yapar ).
ComicSansMS

1
Ne yazık ki, bu başka bir yol (bunların bir kısmının zaten aklın ötesinde olduğunu kabul ediyorum). Kesin olmak gerekirse: Verdiğiniz nedenlerin, akımın std::unique_ptruygun olmayan bir kayıtta geçirilmesine izin veren akla gelebilecek herhangi bir ABI'yi zorunlu kılacağından emin değilim .
ComicSansMS

3
"önemsiz yıkıcı [İHTİYAÇ GEREKİR]" açıkça yanlış; hiçbir kod adrese bağlı değilse as-if adresin gerçek makinede bulunması gerekmez . Adres bulunmalıdır soyut makinede , ancak gerçek makine üzerinde herhangi bir etkisi yoktur soyut makinede şeyler şeylerdir sanki ortadan kaldırmak için izin verilir.
Yakk - Adam Nevraumont

2
@ einpoklum Standartta kayıtların var olduğu hiçbir şey yoktur. Register anahtar kelimesi sadece "adresi alamazsınız" ifadesini kullanır. Standart söz konusu olduğunda sadece soyut bir makine var. "sanki" herhangi bir gerçek makine uygulamasının sadece soyut makinenin standart tarafından tanımlanmayan davranışa kadar davrandığı gibi davranması gerektiği anlamına gelir. Şimdi, bir kayıtta bir nesneye sahip olmak konusunda herkesin kapsamlı olarak konuştuğu çok zor problemler var. Ayrıca, standardın tartışamadığı çağırma sözleşmelerinin pratik ihtiyaçları vardır.
Yakk - Adam Nevraumont

1
@ einpoklum Hayır, soyut makinede her şeyin adresleri var; ancak adresler yalnızca belirli durumlarda gözlemlenebilir. registerAnahtar kelime pratikte zor fiziksel makinede "hayır adresi" yapmak şeyler engelleyerek bir kayıtta mağaza şeye fiziksel makine için Önemsiz yapmak için tasarlanmıştı.
Yakk - Adam Nevraumont

2

Bu aslında bazı platformlarda ABI gereksinimi midir? (hangisi?) Ya da belki belirli senaryolarda biraz kötümserlik?

Uyum birimi biriminde bir şey görünürse, örtülü veya açık olarak tanımlanıp tanımlanmadığı ABI'nin bir parçası haline gelir.

ABI neden böyle?

Temel sorun, çağrı yığınında aşağı ve yukarı hareket ettikçe kayıtların her zaman kaydedilmesi ve geri yüklenmesi. Bu yüzden onlara bir referans veya işaretçi olması pratik değildir.

In-astar ve bundan kaynaklanan optimizasyonlar gerçekleştiğinde güzel, ancak bir ABI tasarımcısı bunun gerçekleşmesine güvenemez. En kötü durumu kabul ederek ABI'yi tasarlamak zorundalar. Programcıların ABI'nin optimizasyon seviyesine bağlı olarak değiştiği bir derleyiciden çok memnun olacağını düşünmüyorum.

Mantıksal kopyalama işlemi iki bölüme ayrılabileceğinden, kayıtlara önemsiz derecede kopyalanabilir bir tür iletilebilir. Parametreler, arayan tarafından parametrelerin iletilmesi için kullanılan kayıtlara kopyalanır ve daha sonra arayan tarafından yerel değişkene kopyalanır. Yerel değişkenin bir bellek konumuna sahip olup olmadığı bu nedenle sadece callee'nin endişesidir.

Öte yandan bir kopyalama veya taşıma kurucusunun kullanılması gereken bir tür, kopyalama işleminin bu şekilde bölünmesini sağlayamaz, bu nedenle bellekte geçirilmesi gerekir.

C ++ standartlar komitesi bu noktayı son yıllarda veya hiç tartıştı mı?

Standart kurumların bunu düşünüp düşünmediği hakkında hiçbir fikrim yok.

Benim için bariz çözüm, dil için uygun yıkıcı hareketler ("geçerli ama başka türlü belirtilmemiş bir devletin şu anki yarı-evinden ziyade) eklemek, sonra" önemsiz yıkıcı hareketlere izin vermek için bir tür işaretlemek için bir yol tanıtmak "önemsiz kopyalara izin vermese bile.

ancak böyle bir çözüm, mevcut tipler için uygulamak üzere mevcut kodun ABI'sının kırılmasını gerektirebilir, bu da oldukça fazla direnç getirebilir (ancak yeni C ++ standart sürümlerinin bir sonucu olarak ABI kırılmaları benzeri görülmemiş olsa da, örneğin std :: string değişiklikleri) C ++ 11 bir ABI molası verdi ..


Yıkıcı hareketlerin bir unique_ptr'in bir kayıt defterine geçirilmesine ne kadar uygun olacağını açıklayabilir misiniz? Bunun nedeni adreslenebilir depolama gereksiniminin düşürülmesine izin vermesi olabilir mi?
einpoklum

Doğru yıkıcı hamleler önemsiz yıkıcı hamleler kavramının ortaya çıkmasını sağlayacaktır. Bu, söz konusu önemsiz hareketin ABI tarafından bugün önemsiz kopyalar gibi bölünmesine izin verecektir.
plugwash

Yine de, bir derleyicinin parametre geçişini normal bir hareket veya kopya olarak uygulayabileceği ve ardından parametrenin nereden geldiğine bakılmaksızın kayıtların her zaman geçirilebilmesini sağlamak için bir "önemsiz yıkıcı hareket" izleyebileceği bir kural eklemek isteyebilirsiniz.
plugwash

Kayıt boyutu bir işaretçi tutabilir, ancak unique_ptr yapısı nedeniyle? Sizeof (unique_ptr <T>) nedir?
Mel Viso Martinez

@MelVisoMartinez Kafa karıştırıcı unique_ptrve shared_ptranlamsal olabilirsiniz: ctor'a shared_ptr<T>1) ifadeyle statik tip U ile silinecek türetilmiş U nesnesine bir ptr x sağlamanıza izin verir delete x;(böylece burada sanal bir dtor'a ihtiyacınız yoktur) 2) veya özel bir temizleme işlevi bile. Bu, çalışma zamanı durumunun shared_ptrbu bilgiyi kodlamak için kontrol bloğu içinde kullanıldığı anlamına gelir . OTOH unique_ptrböyle bir işlevselliğe sahip değildir ve durumdaki silme davranışını kodlamaz; temizlemeyi özelleştirmenin tek yolu başka bir şablon örneği (başka bir sınıf türü) oluşturmaktır.
curiousguy

-1

Öncelikle değere ve referansa göre geçmenin ne anlama geldiğine geri dönmeliyiz.

Java ve SML gibi diller için, değere göre geçiş basittir (ve referans ile geçiş yoktur), tıpkı bir değişken değerin kopyalanması gibi, tüm değişkenler sadece skaler olduğu ve yerleşik kopya semantiğine sahip oldukları için: ya aritmetik olarak sayılan şeydir C ++ veya "referanslar" (farklı ad ve sözdizimine sahip işaretçiler) yazın.

C'de skaler ve kullanıcı tanımlı tiplerimiz vardır:

  • Skalerlerin kopyalanan sayısal veya soyut bir değeri vardır (işaretçiler sayı değildir, soyut bir değere sahiptir).
  • Toplama türlerinde başlatılmış tüm üyeleri kopyalanır:
    • ürün türleri (diziler ve yapılar) için: özyinelemeli olarak, dizilerin yapılarının ve öğelerinin tüm üyeleri kopyalanır (C işlevi sözdizimi dizileri doğrudan değere göre geçirmeyi mümkün kılmaz, yalnızca bir yapının üyelerini diziler, ancak bu bir ayrıntı ).
    • toplam türleri (birlikler) için: "aktif üye" nin değeri korunur; açıkçası, tüm üyeler başlatılamayacağından üye kopyasına göre üye sıralaması yapılamaz.

C ++ 'da kullanıcı tanımlı tipler, kaynaklarına ve "derin kopya" işlemlerine sahip nesnelerle gerçekten "nesne yönelimli" programlamayı sağlayan kullanıcı tanımlı kopya semantiğine sahip olabilir. Böyle bir durumda, bir kopyalama işlemi gerçekten neredeyse keyfi işlemler yapabilen bir işleve çağrıdır.

C ++ olarak derlenen C yapıları için "kopyalama", derleyici tarafından örtük olarak oluşturulan kullanıcı tanımlı kopyalama işlemini (yapıcı veya atama operatörü) çağırmak olarak tanımlanır. Bu, bir C / C ++ ortak altkümesi programının semantiğinin C ve C ++ 'da farklı olduğu anlamına gelir: C'de bütün bir toplu tip kopyalanır, C ++' da her üyeyi kopyalamak için örtük olarak oluşturulan bir kopyalama işlevi çağrılır; sonuç, her iki durumda da her üyenin kopyalanmasıdır.

(Bence, bir birlik içindeki bir yapı kopyalandığında bir istisna vardır.)

Bir sınıf türü için, yeni bir örnek oluşturmanın tek yolu (sendika kopyaları dışında) bir kurucu aracılığıyladır (önemsiz derleyici oluşturucu kurucular için bile).

Bir değerlik adresini tekli operatör aracılığıyla &alamazsınız, ancak bu bir değerleme nesnesi olmadığı anlamına gelmez; ve tanım olarak bir nesnenin bir adresi vardır ; ve bu adres bir sözdizimi yapısı ile temsil edilir: sınıf türünde bir nesne yalnızca bir kurucu tarafından oluşturulabilir ve bir thisişaretçisi vardır; ancak önemsiz türler için, kullanıcı tarafından yazılmış bir kurucu yoktur, bu nedenle thiskopyanın oluşturulup adlandırılmasından sonraya kadar bir yer yoktur .

Skaler tip için, bir nesnenin değeri, nesnenin depolanan saf matematiksel değer olan nesnenin değeridir.

Bir sınıf türü için, nesnenin bir değerinin tek nüshası, nesnenin başka bir kopyasıdır, bu sadece bir kopya oluşturucu, gerçek bir işlev tarafından yapılabilir (bu işlev çok özel önemsiz olsa da, bunlar bazen yapıcı çağrılmadan oluşturulur). Bu , nesnenin değerinin, bir yürütme tarafından genel program durumunun değişmesinin sonucu olduğu anlamına gelir . Matematiksel olarak erişmez.

Yani değere göre geçmek gerçekten bir şey değildir: daha az güzel olan kopya yapıcı çağrısı ile geçer . Kopya yapıcısının, dahili değişmezlerine (içsel C ++ özellikleri değil soyut kullanıcı özellikleri olan) saygı duyarak, nesne türünün uygun semantiğine göre mantıklı bir "kopya" işlemi gerçekleştirmesi beklenir.

Bir sınıf nesnesinin değerine göre geçiş şu anlama gelir:

  • başka bir örnek oluştur
  • sonra çağrılan işlevin bu örnek üzerinde hareket etmesini sağlayın.

Sorunun, kopyanın kendisinin bir adrese sahip bir nesne olup olmadığıyla bir ilgisi olmadığını unutmayın: tüm işlev parametreleri nesnelerdir ve bir adrese sahiptir (dil anlamsal düzeyinde).

Sorun şudur:

  • kopya, skalerlerde olduğu gibi orijinal nesnenin saf matematiksel değeriyle (gerçek saf değer) başlatılan yeni bir nesnedir ;
  • veya kopya, sınıflarda olduğu gibi orijinal nesnenin değeridir .

Önemsiz bir sınıf türü söz konusu olduğunda, orijinalin üye kopyasının üyesini yine de tanımlayabilirsiniz, böylece kopyalama işlemlerinin önemsizliği nedeniyle orijinalin saf değerini tanımlayabilirsiniz (kopya oluşturucu ve atama). Rasgele özel kullanıcı işlevlerinde böyle değildir: orijinalin bir değeri oluşturulmuş bir kopya olmalıdır.

Sınıf nesneleri arayan tarafından oluşturulmalıdır; bir kurucu resmi olarak bir işaretleyiciye sahiptir, thisancak biçimselcilik burada alakalı değildir: tüm nesnelerin resmi olarak bir adresi vardır, ancak yalnızca adreslerini tamamen yerel olmayan yollarla *&i = 1;kullananların (tamamen yerel adres kullanımı aksine ) iyi tanımlanmış olması gerekir adres.

Bir nesne, bu iki ayrı derlenmiş işlevde bir adrese sahip olması gerekiyorsa, kesinlikle adrese göre iletilmelidir:

void callee(int &i) {
  something(&i);
}

void caller() {
  int i;
  callee(i);
  something(&i);
}

Burada something(address)saf bir işlev veya makro veya printf("%p",arg)adresi depolayamayan veya başka bir varlıkla iletişim kuramayan herhangi bir şey (gibi ) olsa bile , adresin benzersiz intbir özelliğe sahip benzersiz bir nesne için iyi tanımlanması gerektiğinden adrese geçme gereksinimimiz vardır. Kimlik.

Harici bir fonksiyonun kendisine iletilen adresler açısından "saf" olup olmayacağını bilmiyoruz.

İşte potansiyeli olmayan önemsiz yapıcısı veya yıkıcıdaki birinde adresin gerçek kullanım için arayan tarafında olduğu gibi, güvenli, basit yol alarak ve adresini arayan içinde nesne bir kimlik vermek ve pas nedeni muhtemelen yapar adresinin kurucudaki, inşaattan sonra ve yıkıcıdaki önemsiz olmayan kullanımlarının tutarlı olduğundan emin olun : thisnesne varlığı üzerinde aynı görünmelidir.

Diğer herhangi bir işlev gibi önemsiz bir kurucu veya yıkıcı, thisönemsiz şeyler içeren bazı nesneler yapmasa da işaretçiyi değeri üzerinde tutarlılık gerektiren bir şekilde kullanabilir :

struct file_handler { // don't use that class!
    file_handler () { this->fileno = -1; }
    file_handler (int f) { this->fileno = f; }
    file_handler (const file_handler& rhs) {
        if (this->fileno != -1)
            this->fileno = dup(rhs.fileno);
        else
            this->fileno = -1;
    }
    ~file_handler () {
        if (this->fileno != -1)
            close(this->fileno); 
    }
    file_handler &operator= (const file_handler& rhs);
};

Bu durumda, bir işaretçinin (açık sözdizimi this->) açıkça kullanılmasına rağmen , nesne kimliğinin alakasız olduğuna dikkat edin: derleyici, nesneyi taşımak ve "kopya elizyonu" yapmak için nesneyi bitsel olarak kopyalayabilir. Bu, thisözel üye işlevlerinde (adres kaçmaz) "saflık" düzeyine dayanır .

Ancak saflık, standart bildirim düzeyinde kullanılabilen bir özellik değildir (satıriçi olmayan işlev bildirimine saflık açıklaması ekleyen derleyici uzantıları vardır), bu nedenle kullanılamayan kodun saflığına dayalı bir ABI tanımlayamazsınız (kod veya satır içi ve analiz için uygun olmayabilir).

Saflık "kesinlikle saf" veya "saf olmayan veya bilinmeyen" olarak ölçülür. Anlamsal zemin veya anlambilimin üst sınırı (aslında maksimum) veya LCM (En Küçük Ortak Çoklu) "bilinmiyor" dur. Böylece ABI bilinmeyene karar verir.

Özet:

  • Bazı yapılar derleyicinin nesne kimliğini tanımlamasını gerektirir.
  • ABI, optimize edilebilecek belirli durumlar değil, program sınıfları açısından tanımlanır.

Gelecekteki olası çalışmalar:

Saflık açıklaması genelleştirilecek ve standartlaştırılacak kadar yararlı mı?


1
İlk örneğiniz yanıltıcı görünüyor. Bence genel olarak bir noktaya değiniyorsunuz, ama ilk başta sorudaki koda bir benzetme yaptığınızı düşündüm . Ancak void foo(unique_ptr<int> ptr)sınıf nesnesini değere göre alır . Bu nesnenin bir işaretçi üyesi var, ancak sınıf nesnesinin kendisinin referans olarak iletildiğinden bahsediyoruz. (Çünkü önemsiz bir şekilde kopyalanamaz, bu yüzden yapıcısının / yıkıcısının tutarlı olması gerekir this.) Bu gerçek argümandır ve açıkça referansla geçmenin ilk örneğine bağlı değildir ; bu durumda işaretçi bir kayıt defterine geçirilir.
Peter Cordes

@PeterCordes "Söz konusu koda bir benzetme yapıyordunuz. " Bunu tam olarak yaptım. " sınıf nesnesi değere göre " Evet muhtemelen genel olarak bir sınıf nesnesinin "değer" diye bir şey olmadığını açıklamalıyım, bu nedenle matematik olmayan bir tür için değere göre "değere göre" değildir. " Bu nesnenin bir işaretçi üyesi var " Bir "akıllı ptr" in ptr benzeri doğası önemsiz; ve "akıllı ptr" in ptr üyesi de öyle. Bir ptr sadece aşağıdaki gibi bir intskalerdir: "Sahiplik" in "ptr taşıma" ile hiçbir ilgisi olmadığını gösteren "akıllı dosya" örneği yazdım.
curiousguy

1
Sınıf nesnesinin değeri, nesne temsilidir. Çünkü unique_ptr<T*>bu, T*bir kayıttaki boyut ve düzen ile aynıdır . Önemsiz olarak kopyalanabilir sınıf nesneleri, çoğu çağrı kuralı gibi, x86-64 Sistem V'deki kayıtlardaki değere göre geçirilebilir . Bu kılan kopyasını arasında unique_ptrsizin aksine nesne intAranan en örnek &i olan arayanın adresi var ibaşvuruyla geçirildi çünkü C ++ düzeyinde değil, sadece bir asm uygulama ayrıntı olarak,.
Peter Cordes

1
Hata, son yorumuma düzeltme. Bu sadece bir katılmıyormuş kopyasını ait unique_ptrnesne; kullanması std::movegüvenlidir, çünkü aynı 2 kopyaya neden olmaz unique_ptr. Ancak önemsiz olarak kopyalanabilen bir tür için, evet, tüm toplu nesneyi kopyalar. Bu tek bir üye ise, iyi çağrı kuralları buna benzer bir skaler gibi davranır.
Peter Cordes

1
Daha iyi görünüyor. Notlar: C ++ olarak derlenen C yapıları için - Bu, C ++ arasındaki farkı tanıtmak için yararlı bir yol değildir. C ++ ' struct{}da bir C ++ yapısıdır. Belki de "düz yapılar" veya "C'nin aksine" demelisiniz. Çünkü evet, bir fark var. atomic_intBir yapı üyesi olarak kullanırsanız , C atomik olmayan bir şekilde kopyalar, silinmiş kopya yapıcısında C ++ hatası verir. C ++ volatileüyeleri ile yapılarda ne yaptığını unutuyorum . C struct tmp = volatile_struct;, her şeyi kopyalamanıza izin verecektir (bir SeqLock için yararlıdır); C ++ olmaz.
Peter Cordes
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.