Kopya seçimi ve iade değeri optimizasyonu nedir?


377

Kopya seçimi nedir? Dönüş değeri optimizasyonu (adlandırılmış) nedir? Ne ima ediyorlar?

Hangi durumlarda ortaya çıkabilirler? Sınırlamalar nelerdir?


1
Kopya seçimleri ona bakmanın bir yoludur; nesne seçimi veya nesne füzyonu (veya karışıklık) başka bir görünümdür.
curiousguy

Bu bağlantıyı faydalı buldum .
subtleseeker

Yanıtlar:


246

Giriş

Teknik bir genel bakış için - bu cevaba atlayın .

Kopya seçiminin meydana geldiği yaygın durumlar için - bu cevaba atlayın .

Kopya elizyonu, belirli durumlarda ekstra (potansiyel olarak pahalı) kopyaları önlemek için çoğu derleyici tarafından uygulanan bir optimizasyondur. Pratikte değere göre veya değere göre geri dönüşü mümkün kılar (kısıtlamalar geçerlidir).

Nesne kopyalama / taşıma işleminin yan etkileri olsa bile , as-if kuralını uygulayan (ha!) Tek optimizasyon şeklidir .

Aşağıdaki örnek Wikipedia'dan alınmıştır :

struct C {
  C() {}
  C(const C&) { std::cout << "A copy was made.\n"; }
};

C f() {
  return C();
}

int main() {
  std::cout << "Hello World!\n";
  C obj = f();
}

Derleyiciye ve ayarlara bağlı olarak, aşağıdaki çıkışların tümü geçerlidir :

Selam Dünya!
Bir kopya çıkarıldı.
Bir kopya çıkarıldı.


Selam Dünya!
Bir kopya çıkarıldı.


Selam Dünya!

Bu aynı zamanda daha az nesnenin oluşturulabileceği anlamına gelir, bu nedenle çağrılan belirli sayıda yıkıcıya da güvenemezsiniz. Kopyalama / taşıma yapıcıları veya yıkıcıları içinde kritik mantığa sahip olmamanız gerekir;

Bir kopyala veya taşı kurucuya çağrı yapılıyorsa, o kurucu hala var olmalı ve erişilebilir olmalıdır. Bu, kopyalama seçiminin, örneğin özel veya silinmiş bir kopyalama / taşıma yapıcısı olduğu için normal olarak kopyalanamayan nesneleri kopyalamaya izin vermemesini sağlar.

C ++ 17 : C ++ 17'den itibaren, bir nesne doğrudan döndürüldüğünde Kopya Seçimi garanti edilir:

struct C {
  C() {}
  C(const C&) { std::cout << "A copy was made.\n"; }
};

C f() {
  return C(); //Definitely performs copy elision
}
C g() {
    C c;
    return c; //Maybe performs copy elision
}

int main() {
  std::cout << "Hello World!\n";
  C obj = f(); //Copy constructor isn't called
}

2
2. çıktının ne zaman ve 3. çıkışın ne olduğunu açıklayabilir misiniz?
zhangxaochen

3
@zhangxaochen derleyici ne zaman ve nasıl bu şekilde optimize etmeye karar verir.
Luchian Grigore

10
@zhangxaochen, 1. çıktı: kopya 1 bir sıcaklıktan dönüşe ve 2 kopyasını sıcaklıktan obj'e; 2, yukarıdakilerden biri optimize edildiğinde, muhtemelen reutnr kopyası kaldırılır; thris her ikisi de elided
victor

2
Hmm, ama bence, bu güvenebileceğimiz bir özellik olmalı. Çünkü yapamazsak, modern C ++ 'da (RVO vs std :: move) fonksiyonlarımızı uygulama şeklimizi ciddi şekilde etkiler. Bazı CppCon 2014 videolarını izlerken, tüm modern derleyicilerin her zaman RVO yaptığı izlenimini aldım. Ayrıca, herhangi bir optimizasyon olmadan da derleyicilerin uyguladığı bir yerde okudum. Ama, elbette, bundan emin değilim. Bu yüzden soruyorum.
j00hi

8
@ j00hi: Bir dönüş ifadesinde asla hareket yazma - rvo uygulanmadıysa, dönüş değeri yine de varsayılan olarak taşınır.
MikeMB

96

Standart referans

Daha az teknik bir görünüm ve tanıtım için - bu cevaba atlayın .

Kopya seçiminin meydana geldiği yaygın durumlar için - bu cevaba atlayın .

Kopya seçimi standartta şu şekilde tanımlanır:

12.8 Sınıf nesnelerini kopyalama ve taşıma [class.copy]

gibi

31) Belirli ölçütler karşılandığında, bir nesnenin kopyalama / taşıma yapıcısı ve / veya yıkıcısının yan etkileri olsa bile bir uygulamanın sınıf nesnesinin kopyala / taşı yapısını atlamasına izin verilir. Bu gibi durumlarda, uygulama, atlanan kopyalama / taşıma işleminin kaynağını ve hedefini aynı nesneye göndermenin yalnızca iki farklı yolu olarak ele alır ve bu nesnenin imhası, iki nesnenin daha sonra optimizasyon olmadan yok edildi. 123 Kopyalama elüsyonu adı verilen bu kopyalama / taşıma işlemlerine, aşağıdaki durumlarda izin verilir (birden fazla kopyayı ortadan kaldırmak için birleştirilebilir):

- sınıf dönüş türüne sahip bir işlevdeki bir return deyiminde, ifade işlev dönüş türüyle aynı niteliksel türe sahip geçici olmayan bir otomatik nesnenin (bir işlev veya catch-clause parametresi dışında) adı olduğunda, kopyalama / taşıma işlemi, otomatik nesnenin doğrudan işlevin dönüş değerine yapılandırılmasıyla çıkarılabilir

- bir atma ifadesinde, işlenen, kapsamı en içteki try-block'un (eğer varsa) ötesine geçmeyen, uçucu olmayan bir otomatik nesnenin (bir işlev veya catch-yan tümcesi parametresi dışında) adı olduğunda bir), işlenenden istisna nesnesine (15.1) kopyalama / taşıma işlemi, otomatik nesne doğrudan istisna nesnesine inşa edilerek atlanabilir

- bir referansa (12.2) bağlı olmayan bir geçici sınıf nesnesi, aynı cv-niteliksiz türüne sahip bir sınıf nesnesine kopyalanır / taşınırsa, kopyalama / taşıma işlemi geçici nesne doğrudan Atlanan kopyanın / taşınmanın hedefi

- bir istisna işleyicisinin istisna-bildirimi (Madde 15) istisna nesnesi (15.1) ile aynı tipte bir nesne (cv-nitelendirmesi hariç) beyan ettiğinde, istisna bildirimi işlenerek kopyalama / taşıma işlemi atlanabilir kural dışı durum bildirimi tarafından bildirilen nesne için yapıcıların ve yıkıcıların yürütülmesi dışında, programın anlamı değişmeyecekse, istisna nesnesi için bir takma ad olarak.

123) İki yerine yalnızca bir nesne yok edildiğinden ve bir kopyala / taşı yapıcı çalıştırılmadığından, inşa edilen her biri için hala bir nesne yok edilir.

Verilen örnek şöyledir:

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
Thing f() {
  Thing t;
  return t;
}
Thing t2 = f();

ve açıkladı:

Burada seçim kriterleri, sınıfın kopya yapıcısına yapılan iki çağrıyı ortadan kaldırmak için birleştirilebilir Thing: tişlevin dönüş değeri için yerel otomatik nesnenin geçici nesneye f() kopyalanması ve bu geçici nesnenin nesneye kopyalanması t2. Etkili bir şekilde, yerel nesnenin inşası t doğrudan global nesneyi başlatan olarak görülebilir t2ve bu nesnenin imhası program çıkışında gerçekleşir. Thing'e bir hareket yapıcısı eklemek de aynı etkiye sahiptir, ancak geçici nesneden t2elenen taşıma hareketidir.


1
Bu C ++ 17 standardından mı yoksa önceki bir sürümden mi?
Nils

90

Ortak kopya eleme şekilleri

Teknik bir genel bakış için - bu cevaba atlayın .

Daha az teknik bir görünüm ve tanıtım için - bu cevaba atlayın .

(Adlandırılmış) Dönüş değeri optimizasyonu yaygın bir kopya eleme şeklidir. Bir yöntemden değere göre döndürülen bir nesnenin kopyasını elenmiş olduğu duruma işaret eder. Standartta belirtilen örnek , nesne adlandırıldığından adlandırılmış dönüş değeri optimizasyonunu gösterir .

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
Thing f() {
  Thing t;
  return t;
}
Thing t2 = f();

Geçici döndürüldüğünde düzenli dönüş değeri optimizasyonu gerçekleşir:

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
Thing f() {
  return Thing();
}
Thing t2 = f();

Kopya seçiminin gerçekleştiği diğer yaygın yerler, geçici olarak değerin geçtiği zamandır :

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
void foo(Thing t);

foo(Thing());

veya değere göre bir istisna atıldığında veya yakalandığında :

struct Thing{
  Thing();
  Thing(const Thing&);
};

void foo() {
  Thing c;
  throw c;
}

int main() {
  try {
    foo();
  }
  catch(Thing c) {  
  }             
}

Kopya seçiminin genel sınırlamaları şunlardır:

  • çoklu dönüş noktaları
  • koşullu başlatma

Çoğu ticari sınıf derleyici kopya seçimini ve (N) RVO'yu destekler (optimizasyon ayarlarına bağlı olarak).


4
"Ortak sınırlamalar" kurşun noktalarının biraz açıklandığını görmek isterim ... bu sınırlayıcı faktörleri ne yapar?
phonetagger

@ phonetagger msdn makalesine bağlandım, umarım bazı şeyleri temizler.
Luchian Grigore

54

Kopya eleme, nesnelerin gereksiz kopyalanmasını / taşınmasını ortadan kaldıran bir derleyici optimizasyon tekniğidir.

Aşağıdaki durumlarda, bir derleyicinin kopyalama / taşıma işlemlerini atlamasına ve dolayısıyla ilişkili kurucuyu çağırmamasına izin verilir:

  1. NRVO (Adlandırılmış Dönüş Değeri Optimizasyonu) : Bir işlev değere göre bir sınıf türünü döndürüyorsa ve return ifadesinin ifadesi, otomatik depolama süresine (bir işlev parametresi olmayan) sahip kalıcı olmayan bir nesnenin adıysa , kopyala / taşı optimize edilmeyen bir derleyici tarafından gerçekleştirilecek olan atlanabilir. Öyleyse, döndürülen değer doğrudan işlevin dönüş değerinin başka türlü taşınacağı veya kopyalanacağı depolama alanında oluşturulur.
  2. RVO (Dönüş Değeri Optimizasyonu) : İşlev, naif bir derleyici tarafından hedefe taşınacak veya kopyalanacak isimsiz bir geçici nesne döndürürse, kopyalama veya taşıma 1'e göre atlanabilir.
#include <iostream>  
using namespace std;

class ABC  
{  
public:   
    const char *a;  
    ABC()  
     { cout<<"Constructor"<<endl; }  
    ABC(const char *ptr)  
     { cout<<"Constructor"<<endl; }  
    ABC(ABC  &obj)  
     { cout<<"copy constructor"<<endl;}  
    ABC(ABC&& obj)  
    { cout<<"Move constructor"<<endl; }  
    ~ABC()  
    { cout<<"Destructor"<<endl; }  
};

ABC fun123()  
{ ABC obj; return obj; }  

ABC xyz123()  
{  return ABC(); }  

int main()  
{  
    ABC abc;  
    ABC obj1(fun123());//NRVO  
    ABC obj2(xyz123());//NRVO  
    ABC xyz = "Stack Overflow";//RVO  
    return 0;  
}

**Output without -fno-elide-constructors**  
root@ajay-PC:/home/ajay/c++# ./a.out   
Constructor    
Constructor  
Constructor  
Constructor  
Destructor  
Destructor  
Destructor  
Destructor  

**Output with -fno-elide-constructors**  
root@ajay-PC:/home/ajay/c++# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors    
root@ajay-PC:/home/ajay/c++# ./a.out   
Constructor  
Constructor  
Move constructor  
Destructor  
Move constructor  
Destructor  
Constructor  
Move constructor  
Destructor  
Move constructor  
Destructor  
Constructor  
Move constructor  
Destructor  
Destructor  
Destructor  
Destructor  
Destructor  

Kopya eleme gerçekleştiğinde ve kopyala / taşı-kurucu çağrılmasa bile, mevcut olmalı ve erişilebilir olmalıdır (hiç optimizasyon olmamış gibi), aksi takdirde program kötü biçimlendirilmiştir.

Bu tür kopya seçimine yalnızca yazılımınızın gözlemlenebilir davranışını etkilemeyeceği yerlerde izin vermelisiniz. Kopya seçimi, gözlemlenebilir yan etkilere (yani elide) izin verilen tek optimizasyon şeklidir. Misal:

#include <iostream>     
int n = 0;    
class ABC     
{  public:  
 ABC(int) {}    
 ABC(const ABC& a) { ++n; } // the copy constructor has a visible side effect    
};                     // it modifies an object with static storage duration    

int main()   
{  
  ABC c1(21); // direct-initialization, calls C::C(42)  
  ABC c2 = ABC(21); // copy-initialization, calls C::C( C(42) )  

  std::cout << n << std::endl; // prints 0 if the copy was elided, 1 otherwise
  return 0;  
}

Output without -fno-elide-constructors  
root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp  
root@ajay-PC:/home/ayadav# ./a.out   
0

Output with -fno-elide-constructors  
root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors  
root@ajay-PC:/home/ayadav# ./a.out   
1

GCC, -fno-elide-constructorskopya seçimini devre dışı bırakma seçeneği sunar. Olası kopya seçiminden kaçınmak istiyorsanız, kullanın -fno-elide-constructors.

Artık neredeyse tüm derleyiciler, optimizasyon etkinleştirildiğinde (ve devre dışı bırakmak için başka bir seçenek ayarlanmadıysa) kopya seçimini sağlar.

Sonuç

Her kopya seçiminde, bir yapı ve kopyanın eşleşen bir imhası atlanır, böylece CPU zamanından tasarruf edilir ve bir nesne yaratılmaz, böylece yığın çerçevesinde yer tasarrufu sağlanır.


6
ifade ABC obj2(xyz123()); NRVO veya RVO mu? geçici değişken / nesne aynı değil elde değil ABC xyz = "Stack Overflow";//RVO
Asif Mushtaq

3
RVO'nun daha somut bir örneğine sahip olmak için, derleyicinin oluşturduğu düzene başvurabilirsiniz (dif'yi görmek için derleyici bayrağını -fno-elide-constructors'ı değiştirin). godbolt.org/g/Y2KcdH
Gab 是 好人
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.