Bunun neden iyi bir model olduğunu anlamak için hem C ++ 03 hem de C ++ 11'deki alternatifleri incelemeliyiz.
Aşağıdakileri almak için C ++ 03 yöntemimiz var std::string const&
:
struct S
{
std::string data;
S(std::string const& str) : data(str)
{}
};
bu durumda, her zaman tek bir kopya gerçekleştirilir. Ham bir C dizesinden inşa ederseniz, a std::string
oluşturulur, sonra tekrar kopyalanır: iki ayırma.
std::string
A'ya referans almak ve sonra onu yerel olarak değiştirmek için C ++ 03 yöntemi vardır std::string
:
struct S
{
std::string data;
S(std::string& str)
{
std::swap(data, str);
}
};
bu, "taşıma anlambiliminin" C ++ 03 sürümüdür ve swap
genellikle yapılması çok ucuz olacak şekilde optimize edilebilir (a gibi move
). Aynı zamanda bağlam içinde de analiz edilmelidir:
S tmp("foo"); // illegal
std::string s("foo");
S tmp2(s); // legal
ve sizi geçici olmayanı oluşturmaya zorlar std::string
, sonra onu atın. (Geçici std::string
, const olmayan bir referansa bağlanamaz). Ancak yalnızca bir tahsis yapılır. C ++ 11 sürümü a alır &&
ve onu std::move
geçici olarak veya geçici olarak çağırmanızı gerektirir: bu, arayanın açıkça çağrının dışında bir kopya oluşturmasını ve bu kopyayı işleve veya kurucuya taşımasını gerektirir.
struct S
{
std::string data;
S(std::string&& str): data(std::move(str))
{}
};
Kullanım:
S tmp("foo"); // legal
std::string s("foo");
S tmp2(std::move(s)); // legal
Ardından, hem kopyayı hem de move
şunları destekleyen tam C ++ 11 sürümünü yapabiliriz :
struct S
{
std::string data;
S(std::string const& str) : data(str) {} // lvalue const, copy
S(std::string && str) : data(std::move(str)) {} // rvalue, move
};
Daha sonra bunun nasıl kullanıldığını inceleyebiliriz:
S tmp( "foo" ); // a temporary `std::string` is created, then moved into tmp.data
std::string bar("bar"); // bar is created
S tmp2( bar ); // bar is copied into tmp.data
std::string bar2("bar2"); // bar2 is created
S tmp3( std::move(bar2) ); // bar2 is moved into tmp.data
Bu 2 aşırı yükleme tekniğinin, yukarıdaki iki C ++ 03 stilinden daha fazla olmasa da en az o kadar verimli olduğu oldukça açıktır. Bu 2-aşırı yüklemeli versiyonu "en uygun" versiyon olarak adlandıracağım.
Şimdi, kopyalayıp kopyalanan sürümü inceleyeceğiz:
struct S2 {
std::string data;
S2( std::string arg ):data(std::move(x)) {}
};
bu senaryoların her birinde:
S2 tmp( "foo" ); // a temporary `std::string` is created, moved into arg, then moved into S2::data
std::string bar("bar"); // bar is created
S2 tmp2( bar ); // bar is copied into arg, then moved into S2::data
std::string bar2("bar2"); // bar2 is created
S2 tmp3( std::move(bar2) ); // bar2 is moved into arg, then moved into S2::data
Bunu yan yana "en uygun" sürümle karşılaştırırsanız, tam olarak bir tane daha yapıyoruz move
! Fazladan bir kez yapmayız copy
.
Öyleyse move
bunun ucuz olduğunu varsayarsak , bu sürüm bize en uygun sürümle neredeyse aynı performansı verir, ancak 2 kat daha az kod.
Ve eğer 2 ila 10 argüman alıyorsanız, koddaki azalma üsteldir - 1 bağımsız değişkenle 2 kat daha az, 2 ile 4x, 3 ile 16x, 4 ile 16x, 10 bağımsız değişkenle 1024x.
Şimdi, mükemmel yönlendirme ve SFINAE yoluyla, 10 argüman alan tek bir kurucu veya işlev şablonu yazmanıza izin vererek, argümanların uygun türlerde olmasını sağlamak için SFINAE yapar ve sonra bunları içine taşır veya kopyalarız. gerektiği gibi yerel eyalet. Bu, program boyutu sorunundaki bin kat artışı önlerken, yine de bu şablondan oluşturulan bir dizi işlev olabilir. (şablon işlev somutlaştırmaları işlevler üretir)
Ve çok sayıda üretilen işlev, daha büyük yürütülebilir kod boyutu anlamına gelir ve bu da performansı düşürebilir.
Birkaç move
saniyenin maliyetine, daha kısa kod ve neredeyse aynı performans elde ediyoruz ve genellikle kodu anlamak daha kolay.
Şimdi, bu sadece işe yarıyor, çünkü fonksiyon (bu durumda bir yapıcı) çağrıldığında, bu argümanın yerel bir kopyasını isteyeceğimizi biliyoruz. Buradaki fikir şudur ki, eğer bir kopya yapacağımızı bilirsek, arayan kişiye onu argüman listemize ekleyerek bir kopya yaptığımızı bildirmeliyiz. Daha sonra bize bir kopya verecekleri gerçeği etrafında optimizasyon yapabilirler (örneğin argümanımıza geçerek).
'Değere göre alma' tekniğinin bir başka avantajı da, genellikle kurucuları hareket ettirmenin istisnasız olmasıdır. Bu, değer olarak alan ve argümanlarından çıkan işlevlerin çoğu zaman hariç tutulabileceği anlamına gelir, herhangi bir throw
s'yi vücutlarından dışarıya ve çağrı kapsamına taşır. (bazen doğrudan inşa yoluyla kim kaçınabilir veya move
fırlatmanın nerede gerçekleşeceğini kontrol etmek için öğeleri ve argümanın içine inşa eder ) Atışsız yöntemler yapmak çoğu zaman buna değer.