Doğru gözlemlere ancak yanlış yoruma sahip olduğuna inanıyorum!
Her normal akıllı derleyici bu durumda (N) RVO kullanacağından, değer döndürülerek kopyalama gerçekleşmez . C ++ 17'den bu zorunludur, bu nedenle işlevden yerel olarak üretilen bir vektör döndürerek herhangi bir kopya göremezsiniz.
Tamam, std::vector
inşaat sırasında veya adım adım doldurarak biraz oynayalım ve neler olacak.
Her şeyden önce, her kopyayı veya taşınmayı şu şekilde görünür kılan bir veri türü oluşturalım:
template <typename DATA >
struct VisibleCopy
{
private:
DATA data;
public:
VisibleCopy( const DATA& data_ ): data{ data_ }
{
std::cout << "Construct " << data << std::endl;
}
VisibleCopy( const VisibleCopy& other ): data{ other.data }
{
std::cout << "Copy " << data << std::endl;
}
VisibleCopy( VisibleCopy&& other ) noexcept : data{ std::move(other.data) }
{
std::cout << "Move " << data << std::endl;
}
VisibleCopy& operator=( const VisibleCopy& other )
{
data = other.data;
std::cout << "copy assign " << data << std::endl;
}
VisibleCopy& operator=( VisibleCopy&& other ) noexcept
{
data = std::move( other.data );
std::cout << "move assign " << data << std::endl;
}
DATA Get() const { return data; }
};
Ve şimdi bazı deneyler başlatalım:
using T = std::vector< VisibleCopy<int> >;
T Get1()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec{ 1,2,3,4 };
std::cout << "End init" << std::endl;
return vec;
}
T Get2()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec(4,0);
std::cout << "End init" << std::endl;
return vec;
}
T Get3()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec;
vec.emplace_back(1);
vec.emplace_back(2);
vec.emplace_back(3);
vec.emplace_back(4);
std::cout << "End init" << std::endl;
return vec;
}
T Get4()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec;
vec.reserve(4);
vec.emplace_back(1);
vec.emplace_back(2);
vec.emplace_back(3);
vec.emplace_back(4);
std::cout << "End init" << std::endl;
return vec;
}
int main()
{
auto vec1 = Get1();
auto vec2 = Get2();
auto vec3 = Get3();
auto vec4 = Get4();
// All data as expected? Lets check:
for ( auto& el: vec1 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec2 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec3 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec4 ) { std::cout << el.Get() << std::endl; }
}
Ne gözlemleyebiliriz:
Örnek 1) Başlatıcı listesinden bir vektör oluşturuyoruz ve belki 4 kez yapı ve 4 hareket göreceğimizi umuyoruz. Ama 4 kopya alıyoruz! Bu biraz gizemli geliyor, ama nedeni başlatıcı listesinin uygulanması! Listeden yineleyici const T*
öğelerin taşınmasını imkansız kıldığı için listeden hareket etmesine izin verilmez . Bu konuyla ilgili ayrıntılı bir cevabı burada bulabilirsiniz: initializer_list ve taşıma semantiği
Örnek 2) Bu durumda, başlangıçtaki bir yapıyı ve değerin 4 kopyasını alırız. Bu özel bir şey değil ve ne bekleyebiliriz.
Örnek 3) Ayrıca burada, inşaat ve beklendiği gibi bazı hareketler. Benim stl uygulaması ile vektör her zaman faktör 2 büyür. İlk yapıyı, diğerini görüyoruz ve vektör 1'den 2'ye yeniden boyutlandırdığından, ilk elemanın hareketini görüyoruz. Üçünü eklerken, ilk iki öğenin hareket etmesini gerektiren 2'den 4'e kadar bir yeniden boyutlandırma görüyoruz. Tüm beklendiği gibi!
Örnek 4) Şimdi yer ayırıyoruz ve daha sonra dolduruyoruz. Artık hiçbir kopyamız ve hamlemiz yok!
Her durumda, vektörü arayan kişiye geri döndürerek herhangi bir hareket veya kopya görmüyoruz! (N) RVO gerçekleşiyor ve bu adımda başka bir işlem yapılması gerekmiyor!
Sorunuza geri dönün:
"C ++ sahte kopya işlemleri nasıl bulunur"
Yukarıda görüldüğü gibi, hata ayıklama amacıyla aralarına bir proxy sınıfı ekleyebilirsiniz.
Kopyalayıcıyı özel yapmak pek çok durumda çalışmayabilir, çünkü bazı istediğiniz kopyalara ve gizli olanlara sahip olabilirsiniz. Yukarıdaki gibi, sadece örnek 4'ün kodu özel bir kopyalayıcı ile çalışacaktır! Barışı barışla doldurduğumuz için, eğer örnek 4 en hızlı ise soruya cevap veremem.
Üzgünüm, burada "istenmeyen" kopyaları bulmak için genel bir çözüm sunamıyorum. Kodları aramalar için kazsanız bile memcpy
, memcpy
optimize edilecek gibi bulamazsınız ve kitaplık memcpy
işlevinizi çağırmadan işi yapan bazı montajcı talimatlarını doğrudan görürsünüz .
İpucu küçük bir soruna odaklanmak değil. Gerçek performans sorunlarınız varsa bir profil oluşturup ölçün. O kadar çok potansiyel performans katili var ki, sahte memcpy
kullanıma çok fazla zaman harcamak böyle bir fikir değil gibi görünüyor.
std::vector
herhangi bir şekilde kopyalanmasının amaçlandığı gibi olmadığını kabul etmiyorum . Örneğiniz açık bir kopyayı gösterir vestd::move
bir kopya istediğiniz gibi değilse kendinize önerdiğiniz gibi işlevi uygulamak yalnızca doğal ve doğru yaklaşımdır (yine imho) . Optimizasyon bayrakları açıksa ve vektör değiştirilmezse bazı derleyicilerin kopyalamayı atlayabileceğini unutmayın.