Std :: move () değerleri RV değerlerine nasıl aktarır?


105

Sadece mantığını tam olarak anlamadığımı fark ettim std::move().

İlk başta Google'da araştırdım, ancak std::move()yapısının nasıl çalıştığı değil, nasıl kullanılacağına dair belgeler var gibi görünüyor .

Demek istediğim, şablon üye işlevinin ne olduğunu biliyorum ama std::move()VS2010'daki tanıma baktığımda hala kafa karıştırıcı.

std :: move () tanımı aşağıdadır.

template<class _Ty> inline
typename tr1::_Remove_reference<_Ty>::_Type&&
    move(_Ty&& _Arg)
    {   // forward _Arg as movable
        return ((typename tr1::_Remove_reference<_Ty>::_Type&&)_Arg);
    }

İlk önce tuhaf olan şey, (_Ty && _Arg) parametresidir, çünkü aşağıda gördüğünüz gibi işlevi çağırdığımda,

// main()
Object obj1;
Object obj2 = std::move(obj1);

temelde eşittir

// std::move()
_Ty&& _Arg = Obj1;

Ama zaten bildiğiniz gibi, bir LValue'yu bir RValue referansına doğrudan bağlayamazsınız, bu da bana bunun böyle olması gerektiğini düşündürür.

_Ty&& _Arg = (Object&&)obj1;

Ancak, bu saçma çünkü std :: move () tüm değerler için çalışmalıdır.

Sanırım bunun nasıl çalıştığını tam olarak anlamak için bu yapılara da bir göz atmalıyım.

template<class _Ty>
struct _Remove_reference
{   // remove reference
    typedef _Ty _Type;
};

template<class _Ty>
struct _Remove_reference<_Ty&>
{   // remove reference
    typedef _Ty _Type;
};

template<class _Ty>
struct _Remove_reference<_Ty&&>
{   // remove rvalue reference
    typedef _Ty _Type;
};

Ne yazık ki hala kafa karıştırıcı ve anlamıyorum.

Tüm bunların, C ++ ile ilgili temel sözdizimi becerilerimin olmamasından kaynaklandığını biliyorum. Bunların nasıl tam olarak nasıl çalıştığını bilmek istiyorum ve internette alabileceğim tüm belgeler memnuniyetle karşılanacaktır. (Bunu sadece açıklayabilirsen, bu da harika olacak).


32
@NicolBolas Aksine, sorunun kendisi OP'nin bu ifadede kendisinin altını çizdiğini gösteriyor. Kesinlikle OP'nin temel sözdizimi becerilerini kavraması , soruyu sormalarına bile izin verir.
Kyle Strand

Yine de katılmıyorum - hızlı bir şekilde öğrenebilir veya zaten bilgisayar bilimini veya bir bütün olarak programlamayı iyi bir şekilde kavrayabilirsiniz, C ++ dilinde bile ve yine de sözdizimi ile mücadele edebilirsiniz. Zamanınızı mekaniği, algoritmaları vb. Öğrenmek için harcamak, ancak teknik olarak açıkça ifade edilmek yerine, kopyala / taşı / ileriye gibi şeyleri sığ bir anlayışa sahip olmaktan ziyade, gerektiğinde sözdizimini tazelemek için referanslara güvenmek daha iyidir. Hangi hareketin ne yaptığını bilmeden önce "std :: move'ı hareket ettirmek istediğinizde ne zaman ve nasıl kullanacağınızı" nasıl bileceksiniz?
John P

Sanırım buraya movenasıl uygulandığından ziyade nasıl çalıştığını anlamaya geldim . Bu açıklamayı gerçekten yararlı buluyorum: pagefault.blog/2018/03/01/… .
Anton Daneyko

Yanıtlar:


172

Hareket işleviyle başlıyoruz (biraz temizledim):

template <typename T>
typename remove_reference<T>::type&& move(T&& arg)
{
  return static_cast<typename remove_reference<T>::type&&>(arg);
}

Daha kolay kısımla başlayalım - yani, fonksiyon rvalue ile çağrıldığında:

Object a = std::move(Object());
// Object() is temporary, which is prvalue

ve moveşablonumuz aşağıdaki gibi somutlaştırılır:

// move with [T = Object]:
remove_reference<Object>::type&& move(Object&& arg)
{
  return static_cast<remove_reference<Object>::type&&>(arg);
}

Yana remove_referencedönüştürür T&için Tya T&&kadar Tve Objectbaşvuru değil, bizim son fonksiyondur:

Object&& move(Object&& arg)
{
  return static_cast<Object&&>(arg);
}

Şimdi merak edebilirsiniz: oyuncu kadrosuna ihtiyacımız var mı? Cevap: evet, yapıyoruz. Nedeni basit; isimli rvalue referansı lvalue olarak kabul edilir (ve lvalue'dan rvalue referansına örtük dönüştürme standart olarak yasaklanmıştır).


moveLvalue ile aradığımızda ne olacağı :

Object a; // a is lvalue
Object b = std::move(a);

ve karşılık gelen moveörnekleme:

// move with [T = Object&]
remove_reference<Object&>::type&& move(Object& && arg)
{
  return static_cast<remove_reference<Object&>::type&&>(arg);
}

Yine, remove_referencedönüştürür Object&için Objectve elde ederiz:

Object&& move(Object& && arg)
{
  return static_cast<Object&&>(arg);
}

Şimdi zor kısma geliyoruz: ne anlama Object& &&geliyor ve bu lvalue'ya nasıl bağlanabilir?

Kusursuz iletime izin vermek için, C ++ 11 standardı, aşağıdaki gibi referans daraltma için özel kurallar sağlar:

Object &  &  = Object &
Object &  && = Object &
Object && &  = Object &
Object && && = Object &&

Gördüğünüz gibi, bu kurallar altında Object& &&aslında Object&ldeğerlerin bağlanmasına izin veren düz bir lvalue referansı anlamına gelir .

Nihai işlev böylece:

Object&& move(Object& arg)
{
  return static_cast<Object&&>(arg);
}

bu, rvalue ile yapılan önceki somutlaştırmadan farklı değildir - ikisi de argümanını rvalue referansına çevirir ve sonra onu döndürür. Aradaki fark, ilk somutlaştırmanın yalnızca r değerleriyle kullanılabilmesi, ikincisinin ise ldeğerlerle çalışmasıdır.


Neden remove_referencebiraz daha fazlasına ihtiyacımız olduğunu açıklamak için bu işlevi deneyelim

template <typename T>
T&& wanna_be_move(T&& arg)
{
  return static_cast<T&&>(arg);
}

ve lvalue ile somutlaştırın.

// wanna_be_move [with T = Object&]
Object& && wanna_be_move(Object& && arg)
{
  return static_cast<Object& &&>(arg);
}

Yukarıda belirtilen referans daraltma kurallarını uyguladığımızda, kullanılamayan bir fonksiyon elde ettiğimizi görebilirsiniz move(basitçe söylemek gerekirse, lvalue ile çağırırsınız, lvalue geri alırsınız). Bir şey varsa, bu işlev kimlik işlevidir.

Object& wanna_be_move(Object& arg)
{
  return static_cast<Object&>(arg);
}

3
Güzel cevap. Bir lvalue için değerlendirmek için iyi bir fikir olduğunu anlamak, ancak Tolarak Object&, bu gerçekten yapıldığını bilmiyordum. Sarmalayıcı referansları sunmanın sebebinin bu olduğunu düşündüğüm Tiçin Object, bu durumda da değerlendirmeyi bekliyordum ve std::refya değildi.
Christian Rau

2
template <typename T> void f(T arg)(Wikipedia makalesinde bulunan) ve arasında bir fark vardır template <typename T> void f(T& arg). Birincisi değere çözümlenir (ve eğer referansı geçmek istiyorsanız, onu sarmalısınız std::ref), ikincisi ise her zaman referansa çözümlenir. Ne yazık ki, şablon argüman çıkarımına ilişkin kurallar oldukça karmaşıktır, bu nedenle neden yeniden ele T&&alındığına dair kesin bir mantık sağlayamıyorum Object& &&(ama gerçekten oluyor).
Vitus

1
Ancak, bu doğrudan yaklaşımın işe yaramaması için herhangi bir sebep var mı? template <typename T> T&& also_wanna_be_move(T& arg) { return static_cast<T&&>(arg); }
greggo

1
Bu, remove_reference'in neden gerekli olduğunu açıklıyor, ancak hala işlevin neden T && (T & yerine) almak zorunda olduğunu anlamıyorum. Bu açıklamayı doğru anlarsam, bir T && veya T & almanız fark etmez, çünkü her iki durumda da remove_reference onu T'ye dönüştürecektir, o zaman & &'yi geri ekleyeceksiniz. Öyleyse neden T && yerine ve arayanın bir T & 'yi geçmesine izin vermek için mükemmel yönlendirmeye güvenmek yerine bir T &' yi kabul ettiğinizi (semantik olarak kabul ettiğiniz şeydir) söylemiyorsunuz?
mgiuca

3
@mgiuca: Eğer lvalue'ları std::moveyalnızca rvalues'e atamak istiyorsanız , o zaman evet, T&sorun olmaz. Bu numara çoğunlukla esneklik için yapılır: std::moveher şeyi çağırabilir (r değerleri dahil) ve r değerini geri alabilirsiniz.
Vitus

4

_Ty bir şablon parametresidir ve bu durumda

Object obj1;
Object obj2 = std::move(obj1);

_Ty, "Nesne ve" türüdür

bu nedenle _Remove_reference gereklidir.

Daha çok

typedef Object& ObjectRef;
Object obj1;
ObjectRef&& obj1_ref = obj1;
Object&& obj2 = (Object&&)obj1_ref;

Referansı kaldırmasaydık, yaptığımız gibi olurdu

Object&& obj2 = (ObjectRef&&)obj1_ref;

Ancak ObjectRef &&, obj2'ye bağlayamadığımız Object & 'e indirgenir.

Bu şekilde azaltmasının nedeni, mükemmel yönlendirmeyi desteklemektir. Bu makaleye bakın .


Bu, neden _Remove_reference_gerekli olduğuna dair her şeyi açıklamıyor . Örneğin, Object&typedef olarak sahipseniz ve ona referans alırsanız, yine de elde edersiniz Object&. Bu neden && ile çalışmıyor? Orada olan Bunun da bir cevabı ve mükemmel yönlendirme ile ilgisi var.
Nicol Bolas

2
Doğru. Cevap çok ilginç. A & &&, A & 'ya indirgenir, bu nedenle (ObjectRef &&) obj1_ref'i kullanmaya çalışırsak, bu durumda bunun yerine Object & alırız.
Vaughn Cato
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.