Garantili kopya seçimi nasıl çalışır?


Yanıtlar:


130

Kopya seçiminin bazı durumlarda yapılmasına izin verildi. Bununla birlikte, izin verilmiş olsa bile, kodun yine de kopyası çıkarılmamış gibi çalışabilmesi gerekiyordu. Yani, erişilebilir bir kopya ve / veya taşıma oluşturucu olması gerekiyordu.

Garantili kopya seçimi, bir dizi C ++ kavramını yeniden tanımlar, öyle ki kopyaların / hareketlerin elenebileceği belirli koşullar aslında bir kopyayı / taşımayı hiç teşvik etmez . Derleyici bir kopyayı elden çıkarmıyor; standart, böyle bir kopyalamanın asla olamayacağını söylüyor.

Bu işlevi düşünün:

T Func() {return T();}

Garanti edilmeyen kopya seçim kurallarına göre, bu bir geçici oluşturacak ve sonra bu geçiciden işlevin dönüş değerine geçecektir. O hareket operasyonu olabilir elided edilmesi, ancak Tyine de hiç kullanılmamış olsa bile erişilebilir bir hamle yapıcı olması gerekir.

Benzer şekilde:

T t = Func();

Bu, kopyasının başlatılmasıdır t. Bu, başlangıç tdeğerinin dönüş değeri ile kopyalayacaktırFunc . Ancak, Tçağrılmasa bile yine de bir hareket oluşturucuya sahip olmak gerekir.

Garantili kopya seçimi , bir prvalue ifadesinin anlamını yeniden tanımlar . Pre-C ++ 17, prvalues ​​geçici nesnelerdir. C ++ 17'de, bir prvalue ifadesi yalnızca geçici gerçekleştirebilen bir , ancak henüz geçici değildir.

Prvalue türünden bir nesneyi başlatmak için bir prvalue kullanırsanız, o zaman hiçbir geçici gerçekleşmez. Ne zaman yaparsanreturn T(); , bu, işlevin dönüş değerini bir prvalue aracılığıyla başlatır. Bu işlev döndüğünden T, geçici oluşturulmaz; prvalue'nun ilklendirilmesi, dönüş değerini doğrudan doğrudan başlatır.

Anlaşılması gereken şey, dönüş değeri bir prdeğer olduğu için henüz bir nesne olmadığıdır . Sadece bir nesne için bir başlatıcıdır, tıpkıT() .

Bunu yaptığınızda T t = Func();, dönüş değerinin pr değeri doğrudan nesneyi başlatır t; "geçici oluştur ve kopyala / taşı" aşaması yoktur. Yana Func()bireyin dönüş değeri ile bir prvalue eşdeğerdir T(), tdirekt olarak başlatılırT() size yapmış aynen sankiT t = T() .

Bir prvalue başka bir şekilde kullanılırsa, prvalue geçici bir nesneyi somutlaştırır ve bu o ifadede kullanılır (veya ifade yoksa atılır). Öyleyse, eğer yaptıysanız const T &rt = Func();, prvalue T(), referansı içinde saklanacak olan geçici ( başlatıcı olarak kullanarak ) gerçekleştirirrt olağan geçici ömür boyu uzatma öğeleri ile birlikte .

Kesin seçimin yapmanıza izin verdiği bir şey, hareketsiz nesneleri iade etmektir. Örneğin,lock_guard kopyalanamaz veya taşınamaz, bu nedenle onu değere göre döndüren bir işleve sahip olamazsınız. Ancak garantili kopya seçimi ile bunu yapabilirsiniz.

Garantili seçim, doğrudan başlatma ile de çalışır:

new T(FactoryFunction());

Eğer FactoryFunctiongetiri Tdeğeriyle, bu ifadenin ayrılan belleğe dönüş değeri kopyalamak olmaz. Bunun yerine bellek ayıracak ve ayrılmış belleği doğrudan işlev çağrısı için geri dönüş değeri belleği olarak kullanacaktır .

Dolayısıyla, değere göre dönen fabrika işlevleri, yığın tahsisli belleği, farkında bile olmadan doğrudan başlatabilir. Tabii ki, bu işlevler dahili olarak garantili kopya seçimi kurallarını takip ettikleri sürece . Bir tür prvalue döndürmeleri gerekir T.

Elbette bu da işe yarar:

new auto(FactoryFunction());

Daktilo adlarını yazmaktan hoşlanmıyorsanız.


Yukarıdaki garantilerin yalnızca pr değerler için çalıştığını kabul etmek önemlidir. Yani, adlandırılmış bir değişkeni döndürürken hiçbir garanti alamazsınız :

T Func()
{
   T t = ...;
   ...
   return t;
}

Bu durumda, tyine de erişilebilir bir kopyalama / taşıma yapıcısına sahip olmalıdır. Evet, derleyici kopyalama / taşımayı optimize etmeyi seçebilir. Ancak derleyicinin yine de erişilebilir bir kopyalama / taşıma yapıcısının varlığını doğrulaması gerekir.

Dolayısıyla adlandırılmış dönüş değeri optimizasyonu (NRVO) için hiçbir şey değişmez.


1
@BenVoigt: Önemsiz bir şekilde kopyalanamayan kullanıcı tanımlı türleri yazmaçlara koymak, elizyon mevcut olsa da olmasa da, bir ABI'nin yapabileceği uygun bir şey değildir.
Nicol Bolas

1
Artık kurallar kamuya açık olduğuna göre, bunu "prvalues ​​is initializations" konseptiyle güncellemeye değer olabilir.
Johannes Schaub - litb

7
@ JohannesSchaub-litb: C ++ standardının küçük ayrıntıları hakkında çok fazla şey biliyorsanız, yalnızca "belirsiz" dir. C ++ topluluğunun% 99'u için, "garantili kopya seçiminin" ne anlama geldiğini biliyoruz. Özelliği öneren asıl makale "Garantili Kopya Çıkarma" olarak bile adlandırılıyor . "Basitleştirilmiş değer kategorileri aracılığıyla" eklemek, yalnızca kafa karıştırıcı ve kullanıcıların anlamasını zorlaştırır. Ayrıca bu yanlış bir adlandırma, çünkü bu kurallar değer kategorileri etrafındaki kuralları gerçekten "basitleştirmiyor". Hoşunuza gitsin ya da gitmesin, "garantili kopya seçimi" terimi bu özelliğe atıfta bulunur ve başka hiçbir şeyle ilgili değildir.
Nicol Bolas

1
Bir değer alıp etrafta taşıyabilmeyi çok istiyorum. Sanırım bu sadece bir (tek seferlik) std::function<T()>gerçekten.
Yakk - Adam Nevraumont

1
@LukasSalich: Bu bir C ++ 11 sorusu. Bu cevap bir C ++ 17 özelliği hakkındadır.
Nicol Bolas
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.