C ++ 'da semantiği taşıma - Yerel değişkenlerin hareket-dönüşü


11

Anladığım kadarıyla, C ++ 11'de, bir işlevden değere göre yerel bir değişken döndürdüğünüzde, derleyicinin bu değişkeni bir r değeri başvurusu olarak kabul etmesine ve döndürmek için işlevden 'taşımasına' izin verilir. Bunun yerine RVO / NRVO gerçekleşmez).

Benim sorum şu, bu mevcut kodu kıramaz mı?

Aşağıdaki kodu göz önünde bulundurun:

#include <iostream>
#include <string>

struct bar
{
  bar(const std::string& str) : _str(str) {}
  bar(const bar&) = delete;
  bar(bar&& other) : _str(std::move(other._str)) {other._str = "Stolen";}
  void print() {std::cout << _str << std::endl;}

  std::string _str;
};

struct foo
{
  foo(bar& b) : _b(b) {}
  ~foo() {_b.print();}

  bar& _b;
};

bar foobar()
{
  bar b("Hello, World!");
  foo f(b);

  return std::move(b);
}

int main()
{
  foobar();
  return EXIT_SUCCESS;
}

Düşüncelerim, yerel bir nesnenin yıkıcısının, dolaylı olarak taşınan nesneye referans vermesinin ve dolayısıyla beklenmedik bir şekilde 'boş' bir nesneyi görmesinin mümkün olacağıydı. Ben (bkz Bunu test etmeye çalıştı http://ideone.com/ZURoeT ), ama açık olmadan 'doğru' sonucu aldığım std::movein foobar(). NRVO nedeniyle olduğunu tahmin ediyorum, ama bunu devre dışı bırakmak için kodu yeniden düzenlemeye çalışmadım.

Bu dönüşümün (işlevden çıkmasına neden olan) dolaylı olarak gerçekleşmesi ve var olan kodu kırabilmesinde doğru muyum?

GÜNCELLEME İşte neden bahsettiğimi gösteren bir örnek. Aşağıdaki iki bağlantı aynı kod içindir. http://ideone.com/4GFIRu - C ++ 03 http://ideone.com/FcL2Xj - C ++ 11

Çıktıya bakarsanız, farklıdır.

Yani, sanırım şimdi bu soru haline geldi, bu standarda örtülü hareket eklenirken dikkate alındı ​​ve bu tür bir kod yeterince nadir olduğu için bu kırılma değişikliğini eklemenin tamam olduğuna karar verildi mi? Bu tür durumlarda herhangi bir derleyicinin uyarıp uyarmayacağını merak ediyorum ...


Bir hareket, nesneyi her zaman yıkılabilir bir durumda bırakmalıdır.
Zan Lynx

Evet, ama soru bu değil. Pre-c ++ 11 kodu, yerel bir değişkenin değerinin döndürülmesinden dolayı değişmeyeceğini varsayabilir, bu nedenle bu örtük hareket bu varsayımı kırabilir.
Bwmat

Örneğimde bunu açıklamaya çalıştım; yıkıcılar aracılığıyla bir işlevin yerel değişkenlerinin durumunu (alt kümesinin) 'return' ifadesi çalıştırıldıktan sonra, ancak işlev gerçekte geri dönmeden önce inceleyebilirsiniz.
Bwmat

Bu, eklediğiniz örnekle mükemmel bir soru. Umarım bu, bunu aydınlatabilecek profesyonellerden daha fazla yanıt alır. Verebileceğim tek gerçek geri bildirim: bu yüzden nesnelerin genellikle verilere sahip olmayan görünümleri olmamalıdır. Nesnelere sahip olmayan görünümler (ham işaretçiler veya referanslar) verdiğinizde hata veren masum görünümlü kod yazmanın aslında birçok yolu vardır. İsterseniz bu konuya uygun bir cevap verebilirim, ama sanırım gerçekten duymak istediğiniz şey bu değil. Ve btw, 11'in mevcut kodu, örneğin yeni anahtar kelimeler tanımlayarak bozabileceği zaten biliniyor.
Nir Friedman

Evet, C ++ 11'in herhangi bir eski kodu kırmadığını iddia etmediğini biliyorum, ancak bu oldukça ince ve kaçırması gerçekten kolay olacak (derleyici hataları, uyarılar, segfaults)
Bwmat

Yanıtlar:


8

Scott Meyers , örtük hareket oluşturucu nesillerinin C ++ 03 sınıf değişmezlerini kırabileceği bir sorun hakkında comp.lang.c ++ 'ya (Ağustos 2010) gönderdi :

struct X
{
  // invariant: v.size() == 5
  X() : v(5) {}

  ~X() { std::cout << v[0] << std::endl; }

private:    
  std::vector<int> v;
};

int main()
{
    std::vector<X> y;
    y.push_back(X()); // X() rvalue: copied in C++03, moved in C++0x
}

Burada sorun, C ++ 03'te, üyesinin her zaman 5 öğeye sahip Xolduğu bir değişmez olmasıydı v. X::~X()değişmez, ancak yeni tanıtılan hamle yapıcısı taşındı v, böylece uzunluğunu sıfıra ayarladı.

Bu, örneğinizle ilgilidir, çünkü kırık değişmez yalnızca X'yıkıcısında' tespit edilir (dediğiniz gibi, yerel bir nesnenin yıkıcısının örtülü olarak taşınan nesneye referans vermesi ve bu nedenle beklenmedik bir şekilde boş bir nesne görmesi mümkündür ).

C ++ 11, mevcut kodlardan bazılarını kırmak ve hareket yapıcılarına dayalı yararlı optimizasyonlar sağlamak arasında bir denge kurmaya çalışır.

Komite, başlangıçta hareket kurucuları ve hareket atama operatörlerinin kullanıcı tarafından sağlanmadığında derleyici tarafından oluşturulmasına karar vermiştir.

Daha sonra bunun gerçekten de alarm için bir neden olduğuna karar verdi ve otomatik olarak hareket kurucularının ve hareket atama operatörlerinin, mevcut kodun kırılması imkansız olmasa da (örneğin açıkça tanımlanmış yıkıcı) daha az muhtemel olacağı şekilde kısıtladı.

Kullanıcı tanımlı bir yıkıcı mevcut olduğunda örtük hareket yapıcılarının oluşturulmasının önlenmesinin yeterli olduğunu düşünmek cazip gelebilir , ancak bu doğru değildir ( N3153 - Örtülü Hareket Daha fazla ayrıntı için Gitmeli ).

In N3174 - taşı değil Taşı veya To Stroupstrup diyor ki:

Bunu basit bir geriye dönük uyumluluk probleminden ziyade bir dil tasarım problemi olarak görüyorum. Eski kodu kırmaktan kaçınmak kolaydır (örneğin, yalnızca C ++ 0x'den taşıma işlemlerini kaldırın), ancak bazı C + 'yı kırmaya değecek önemli bir hedef taşıyarak C ++ 0x'i daha iyi bir dil haline getiriyorum. +98 kodu.

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.