Std :: move () nedir ve ne zaman kullanılmalıdır?


656
  1. Bu ne?
  2. Bu ne işe yarıyor?
  3. Ne zaman kullanılmalıdır?

İyi bağlantılar takdir edilmektedir.




12
Bu soru std::move(T && t); std::move(InputIt first, InputIt last, OutputIt d_first)ile ilgili bir algoritma da vardır std::copy. Bunu, diğerlerinin std::moveüç argüman alarak ilk karşılaştığım kadar karışık olmamaları için işaret ediyorum . en.cppreference.com/w/cpp/algorithm/move
josaphatv

Yanıtlar:


287

C ++ 11 R-değeri referansları ve taşıma yapıcıları hakkında Wikipedia Sayfası

  1. C ++ 11'de, kopya oluşturuculara ek olarak, nesneler taşıma yapıcılarına sahip olabilir.
    (Ve kopya atama işleçlerine ek olarak, taşıma atama işleçleri vardır.)
  2. Nesne "rvalue-reference" ( Type &&) türüne sahipse, kopya oluşturucu yerine move yapıcısı kullanılır .
  3. std::move() nesneden taşınmasını sağlamak için nesneye bir rvalue referansı üreten bir dökümdür.

Kopyalardan kaçınmanın yeni bir C ++ yolu. Örneğin, bir taşıma yapıcısı kullanarak, a std::vectordahili işaretçisini yeni nesneye verilere kopyalayabilir, taşınan nesneyi hareket ettirilmiş durumda bırakabilir, bu nedenle tüm verileri kopyalayamaz. Bu C ++ - geçerli olacaktır.

Hareket semantiği, değerleme, mükemmel yönlendirme için googling'i deneyin.


40
Taşıma semantiği, taşınan nesnenin geçerli kalmasını gerektirir , bu yanlış bir durum değildir. (Gerekçe: Hâlâ yıkmak, işe
yaramak zorundadır

13
@GMan: Şey, yıkılması güvenli bir durumda olmalı, ama AFAIK, başka bir şey için kullanılabilir olması gerekmiyor.
Zan Lynx

8
@ZanLynx: Doğru. Standart kitaplığın ayrıca taşınan nesnelerin atanabilir olmasını gerektirdiğini unutmayın, ancak bu yalnızca stdlib'de kullanılan nesneler içindir, genel bir gereklilik değildir.
GManNickG

25
-1 "std :: move (), hareket semantiğini kullanmanın C ++ 11 yoludur" Lütfen bunu düzeltin. std::move()hareket semantiğini kullanmanın bir yolu değildir, hareket semantiği şeffaf olarak programcıya uygulanır. moveorijinal değer değerinin artık kullanılmayacağı bir değeri bir noktadan diğerine geçirmek için kullanılan tek döküm.
Manu343726

15
Daha ileri giderdim. std::movekendisi "hiçbir şey" yapmaz - sıfır yan etkisi vardır. Sadece derleyiciye programcının o nesneye ne olduğunu umursamadığını bildirir. yani , yazılımın diğer bölümlerinin nesneden taşınmasına izin verir , ancak taşınmasını gerektirmez. Aslında, bir rvalue referansının alıcısı, verilerle ne yapacağına veya yapmayacağına dair herhangi bir söz vermek zorunda değildir.
Aaron McDaid

241

1. "Nedir?"

std::move() Teknik olarak bir işlev olsa da - gerçekten bir işlev olmadığını söyleyebilirim . Bu , derleyicinin bir ifadenin değerini dikkate alma yolları arasında bir dönüştürücüdür .

2. "Ne işe yarar?"

Dikkat edilmesi gereken ilk şey, std::move() aslında hiçbir şeyi hareket ettirmemesi . Bir ifadeyi lvalue (adlandırılmış değişken gibi) olmak yerine xvalue olmaya dönüştürür . Bir xvalue derleyiciye şunu söyler:

Beni yağmalayabilir, tuttuğum her şeyi taşıyabilir ve başka bir yerde kullanabilirsiniz (çünkü yakında yok olacağım).

başka bir deyişle, kullandığınızda std::move(x), derleyicinin yamyamlaşmasına izin veriyorsunuz x. Böylece x, örneğin, bellekte kendi arabelleği varsa - std::move()derleyiciyi girdikten sonra bunun yerine başka bir nesne olabilir.

Ayrıca bir ön değerden (geçici olarak geçtiğiniz gibi) hareket edebilirsiniz , ancak bu nadiren yararlıdır.

3. "Ne zaman kullanılmalı?"

Bu soruyu sormanın bir başka yolu da "Mevcut bir nesnenin kaynaklarını ne için yamyam olurum?" uygulama kodu yazıyorsanız, derleyici tarafından oluşturulan geçici nesnelerle çok fazla uğraşmayacaksınızdır. Yani esas olarak bunu yapıcılar, operatör yöntemleri, standart-kütüphane-algoritma benzeri fonksiyonlar gibi yerlerde nesnelerin otomatik olarak çok fazla yaratıldığı ve imha edildiği yerlerde yapardınız. Tabii ki, bu sadece bir kural.

Tipik bir kullanım, kopyalamak yerine kaynakları bir nesneden diğerine 'taşımaktır'. @Guillaume , kısa bir örneği olan bu sayfaya bağlantı veriyor : iki nesneyi daha az kopyalamayla değiştirmek.

template <class T>
swap(T& a, T& b) {
    T tmp(a);   // we now have two copies of a
    a = b;      // we now have two copies of b (+ discarded a copy of a)
    b = tmp;    // we now have two copies of tmp (+ discarded a copy of b)
}

move komutunu kullanmak, kaynakları kopyalamak yerine değiştirmenize olanak tanır:

template <class T>
swap(T& a, T& b) {
    T tmp(std::move(a));
    a = std::move(b);   
    b = std::move(tmp);
}

Ne olur düşünün T, diyelim ki, bir vector<int>n büyüklüğünde. İlk versiyonda 3 * n elemanını okuyup yazıyorsunuz, ikinci versiyonda temelde vektörlerin tamponlarına artı 3 tamponun boyutlarına sadece 3 işaretçi okuyup yazıyorsunuz. Tabii ki, sınıfın Tnasıl hareket edileceğini bilmesi gerekir; bunun için sınıfınızda bir hareket atama işleci ve sınıf Tiçin bir hareket oluşturucu bulunmalıdır.


3
Bu hareket semantiğini uzun zamandır duydum, onlara hiç bakmadım. Verdiğiniz bu açıklamadan, derin bir kopya yerine sığ bir kopya gibi görünüyor.
Zebrafish

7
@TitoneMaurice: Orijinal değer artık kullanılamadığından kopya değil.
einpoklum

3
@Zebrafish daha yanlış olamazdın. Sığ bir kopya orijinali tam olarak aynı durumda bırakır, bir hareket genellikle orijinalin boş olmasına veya başka bir şekilde geçerli bir duruma neden olur.
rubenvb

17
@rubenvb Zebra tamamen yanlış değil. Orijinal kannabilize edilmiş nesnenin genellikle kafa karıştırıcı hataları önlemek için kasıtlı olarak sabote edildiği doğru olsa da (örneğin, artık işaretlere sahip olmadığına işaret etmek için işaretçilerini nullptr olarak ayarlayın), tüm hareketin basitçe bir işaretleyiciyi kaynaktan kopyalayarak uygulanması hedefe (ve kasıtlı olarak sivri uçlu bir şey yapmaktan kaçınmak) sığ bir kopyayı andırıyor. Aslında, bir hareket olduğunu söylemek kadar ileri gider olduğu kaynağının kısmi kendini imha ardından isteğe bağlı basit bir kopyasını. (devamı)
Yörüngedeki Hafiflik Yarışları

3
(devamı) Eğer bu tanıma izin verirsek (ve daha çok hoşuma gidiyor), o zaman @ Zebrafish'in gözlemi yanlış değil, sadece biraz eksik.
Yörüngedeki Hafiflik Yarışları

146

Bir nesneyi başka bir yere kopyalamak zorunda kalmadan "aktarmanız" gerektiğinde move komutunu kullanabilirsiniz (yani içerik çoğaltılmaz, bu yüzden unique_ptr gibi bazı kopyalanamayan nesnelerde kullanılabilir). Bir nesnenin std :: move ile bir kopya yapmadan (ve çok fazla zaman kazanmadan) geçici bir nesnenin içeriğini alması da mümkündür.

Bu bağlantı bana gerçekten yardımcı oldu:

http://thbecker.net/articles/rvalue_references/section_01.html

Cevabım çok geç geliyorsa üzgünüm, ama aynı zamanda std :: move için iyi bir bağlantı arıyordum ve biraz "sade" yukarıdaki bağlantıları buldum.

Bu, r-değeri referansına vurgu yapar, bu bağlamda onları kullanmanız gerekir ve bence daha ayrıntılı, bu yüzden bu bağlantıyı burada paylaşmak istedim.


26
Güzel bağlantı. Her zaman wikipedia makalesini ve karşılaştığım diğer bağlantıları oldukça kafa karıştırıcı buldum çünkü gerçekleri / mantığın ne olduğunu anlamanız için size bırakıyorlar. Bir kurucudaki "semantiği taşı" oldukça açık olsa da, && - değerlerini iletmekle ilgili tüm detaylar ... ... öğretici tarzı açıklaması çok güzeldi.
Christian Stieber

66

S: Nedir std::move?

C: std::move()C ++ Standart Kitaplığı'ndan bir değer referansına döküm için kullanılan bir işlevdir.

Basitçe std::move(t)şuna eşittir:

static_cast<T&&>(t);

Değer, hiçbir zaman bir değişkende depolanmayan bir ara işlev sonucu gibi, onu tanımlayan ifadenin ötesinde kalıcı olmayan bir geçicidir.

int a = 3; // 3 is a rvalue, does not exist after expression is evaluated
int b = a; // a is a lvalue, keeps existing after expression is evaluated

Std :: move () için bir uygulama N2027'de verilmiştir : "Rvalue Referanslarına Kısa Bir Giriş" aşağıdaki gibidir:

template <class T>
typename remove_reference<T>::type&&
std::move(T&& a)
{
    return a;
}

Gördüğünüz gibi, bir değer ( ), başvuru türü ( ) veya değer referansı ( ) ile çağrılırsa std::movedöndürür .T&&TT&T&&

S: Ne işe yarar?

C: Oyuncular olarak, çalışma zamanı boyunca hiçbir şey yapmaz. Sadece derleme zamanında derleyiciye referansı bir değer olarak değerlendirmeyi sürdürmek istediğinizi söylemek önemlidir.

foo(3 * 5); // obviously, you are calling foo with a temporary (rvalue)

int a = 3 * 5;
foo(a);     // how to tell the compiler to treat `a` as an rvalue?
foo(std::move(a)); // will call `foo(int&& a)` rather than `foo(int a)` or `foo(int& a)`

O neyi değil yapın:

  • Argümanın bir kopyasını oluşturun
  • Kopya oluşturucuyu çağırın
  • Bağımsız değişken nesnesini değiştirme

S: Ne zaman kullanılmalıdır?

C: std::moveHareket semantiğini bir değer olmayan bir argümanla (geçici ifade) destekleyen işlevleri çağırmak istiyorsanız kullanmalısınız .

Bu benim için aşağıdaki takip sorularını soruyor:

  • Hareket semantiği nedir? Semantiği kopya semantiğinin aksine taşıma, bir nesnenin üyelerinin başka bir nesnenin üyelerini kopyalamak yerine 'devralarak' başlatıldığı bir programlama tekniğidir. Böyle bir 'devralma' sadece işaretçiler ve kaynak tutamaçlarıyla anlamlıdır, bu da temeldeki veriler yerine işaretçi veya tamsayı tutamacını kopyalayarak ucuz bir şekilde aktarılabilir.

  • Ne tür sınıflar ve nesneler hareket semantiğini destekler? Eğer üyeleri kopyalamak yerine üyelerini aktarmaktan faydalanırsa, kendi sınıflarınızda hareket semantiği uygulamak bir geliştirici olarak size bağlıdır. Hareket semantiği uyguladıktan sonra, hareket semantiği olan sınıfları verimli bir şekilde ele almak için destek ekleyen birçok kütüphane programcısının çalışmalarından doğrudan yararlanacaksınız.

  • Derleyici bunu neden kendi başına anlayamıyor? Derleyici, siz söylemedikçe bir işlevin aşırı yüklenmesini çağıramaz. Derleyiciye, işlevin normal veya taşıma sürümünün çağrılıp çağrılmayacağını seçmesi için yardımcı olmalısınız.

  • Hangi durumlarda derleyiciye bir değişkeni değer olarak değerlendirmesi gerektiğini söylemek isterim? Bu, büyük olasılıkla bir ara sonucun kurtarılabileceğini bildiğiniz şablon veya kütüphane işlevlerinde gerçekleşir.


2
Yorumlarda anlambilim içeren kod örnekleri için büyük +1. Diğer en iyi yanıtlar std :: move komutunu "move" komutunu kullanarak tanımlar - hiçbir şeyi netleştirmez! --- Tartışmanın bir kopyasının yapılmamasının orijinal değerin güvenilir bir şekilde kullanılamayacağı anlamına geldiğini belirtmek gerekir.
ty

34

std :: move kendisi pek bir şey yapmaz. Ben bir nesne için taşınan yapıcı çağırdı düşündüm, ama gerçekten sadece bir tür döküm gerçekleştirir (bir değer değeri bir lvalue değişkeni döküm böylece söz konusu değişken bir hareket yapıcı veya atama operatörüne bir argüman olarak geçirilebilir).

Yani std :: move, sadece hareket semantiğini kullanmak için bir öncü olarak kullanılır. Semantiği taşıma, aslında geçici nesnelerle başa çıkmanın etkili bir yoludur.

Nesneyi Düşünün A = B + C + D + E + F;

Bu hoş görünümlü bir koddur, ancak E + F geçici bir nesne üretir. Sonra D + temp başka bir geçici nesne üretir vb. Bir sınıfın her normal "+" işlecinde derin kopyalar oluşur.

Örneğin

Object Object::operator+ (const Object& rhs) {
    Object temp (*this);
    // logic for adding
    return temp;
}

Bu işlevde geçici nesnenin oluşturulması işe yaramaz - bu geçici nesneler, kapsam dışına çıktıkça, satırın sonunda silinecektir.

Geçici nesneleri "yağmalamak" için hareket semantiğini kullanabiliriz ve

 Object& Object::operator+ (Object&& rhs) {
     // logic to modify rhs directly
     return rhs;
 }

Bu gereksiz derin kopyalar yapılmasını önler. Örneğe istinaden, derin kopyalamanın meydana geldiği tek parça artık E + F'dir. Geri kalanı hareket semantiği kullanır. Sonucu A'ya atamak için hareket oluşturucu veya atama operatörünün de uygulanması gerekir.


3
hareket semantiği hakkında konuştunuz. Cevabınızı std :: move'in nasıl kullanılabileceği için eklemelisiniz çünkü soru bunu soruyor.
Koushik Shetty

2
@Koushik std :: move fazla bir şey yapmaz - ancak taşıma semantiği uygulamak için kullanılır. Std :: move hakkında bilmiyorsanız, muhtemelen hareket semantiğini de bilmiyorsunuz
user929404

1
"fazla bir şey yapmaz" (evet bir rvalue referansına bir static_cast). aslında ne yapar ve y OP'nin sorduğu şeydir. std :: move öğesinin nasıl çalıştığını bilmenize gerek yok, ancak anlam semantiğinin ne yaptığını bilmelisiniz. dahası, "ama hareket semantiği uygulamak için kullanılır" onun tersi. semantik hareketlerini bilir ve std :: anlamazsınız aksi halde hareket eder. hareket sadece harekete yardımcı olur ve kendisi de hareket semantiği kullanır. std :: move, argümanını rvalue referansına dönüştürmekten başka bir şey yapmaz, bu da hareket semantiğinin gerektirdiği şeydir.
Koushik Shetty

10
"ancak E + F geçici bir nesne üretir" - Operatör +sağdan sola değil, sağdan sola gider. Bu yüzden B+Cilk olurdu!
Ajay

8

"Bu ne?" ve "Ne işe yarıyor?" yukarıda açıklanmıştır.

"Ne zaman kullanılmalı" diye bir örnek vereceğim .

Örneğin, içinde büyük dizi gibi çok fazla kaynağa sahip bir sınıfımız var.

class ResHeavy{ //  ResHeavy means heavy resource
    public:
        ResHeavy(int len=10):_upInt(new int[len]),_len(len){
            cout<<"default ctor"<<endl;
        }

        ResHeavy(const ResHeavy& rhs):_upInt(new int[rhs._len]),_len(rhs._len){
            cout<<"copy ctor"<<endl;
        }

        ResHeavy& operator=(const ResHeavy& rhs){
            _upInt.reset(new int[rhs._len]);
            _len = rhs._len;
            cout<<"operator= ctor"<<endl;
        }

        ResHeavy(ResHeavy&& rhs){
            _upInt = std::move(rhs._upInt);
            _len = rhs._len;
            rhs._len = 0;
            cout<<"move ctor"<<endl;
        }

    // check array valid
    bool is_up_valid(){
        return _upInt != nullptr;
    }

    private:
        std::unique_ptr<int[]> _upInt; // heavy array resource
        int _len; // length of int array
};

Test kodu:

void test_std_move2(){
    ResHeavy rh; // only one int[]
    // operator rh

    // after some operator of rh, it becomes no-use
    // transform it to other object
    ResHeavy rh2 = std::move(rh); // rh becomes invalid

    // show rh, rh2 it valid
    if(rh.is_up_valid())
        cout<<"rh valid"<<endl;
    else
        cout<<"rh invalid"<<endl;

    if(rh2.is_up_valid())
        cout<<"rh2 valid"<<endl;
    else
        cout<<"rh2 invalid"<<endl;

    // new ResHeavy object, created by copy ctor
    ResHeavy rh3(rh2);  // two copy of int[]

    if(rh3.is_up_valid())
        cout<<"rh3 valid"<<endl;
    else
        cout<<"rh3 invalid"<<endl;
}

çıktı aşağıdaki gibi:

default ctor
move ctor
rh invalid
rh2 valid
copy ctor
rh3 valid

Biz görebilirsiniz std::moveile move constructormarkaları kolayca kaynak dönüşümü.

Başka nerede std::movefaydalıdır?

std::movebir dizi öğeyi sıralarken de yararlı olabilir. Birçok sıralama algoritması (seçim sıralama ve kabarcık sıralama gibi) eleman çiftlerini değiştirerek çalışır. Daha önce, takas yapmak için kopya-semantiğe başvurmak zorunda kaldık. Artık daha etkili olan semantik hareketlerini kullanabiliriz.

Bir akıllı işaretçi tarafından yönetilen içeriği diğerine taşımak istiyorsak da yararlı olabilir.

Atıf:


0

Aşağıda, (basit) özel bir vektör için std :: move komutunun kullanıldığı tam bir örnek verilmiştir

Beklenen çıktı:

 c: [10][11]
 copy ctor called
 copy of c: [10][11]
 move ctor called
 moved c: [10][11]

Şu şekilde derleyin:

  g++ -std=c++2a -O2 -Wall -pedantic foo.cpp

Kod:

#include <iostream>
#include <algorithm>

template<class T> class MyVector {
private:
    T *data;
    size_t maxlen;
    size_t currlen;
public:
    MyVector<T> () : data (nullptr), maxlen(0), currlen(0) { }
    MyVector<T> (int maxlen) : data (new T [maxlen]), maxlen(maxlen), currlen(0) { }

    MyVector<T> (const MyVector& o) {
        std::cout << "copy ctor called" << std::endl;
        data = new T [o.maxlen];
        maxlen = o.maxlen;
        currlen = o.currlen;
        std::copy(o.data, o.data + o.maxlen, data);
    }

    MyVector<T> (const MyVector<T>&& o) {
        std::cout << "move ctor called" << std::endl;
        data = o.data;
        maxlen = o.maxlen;
        currlen = o.currlen;
    }

    void push_back (const T& i) {
        if (currlen >= maxlen) {
            maxlen *= 2;
            auto newdata = new T [maxlen];
            std::copy(data, data + currlen, newdata);
            if (data) {
                delete[] data;
            }
            data = newdata;
        }
        data[currlen++] = i;
    }

    friend std::ostream& operator<<(std::ostream &os, const MyVector<T>& o) {
        auto s = o.data;
        auto e = o.data + o.currlen;;
        while (s < e) {
            os << "[" << *s << "]";
            s++;
        }
        return os;
    }
};

int main() {
    auto c = new MyVector<int>(1);
    c->push_back(10);
    c->push_back(11);
    std::cout << "c: " << *c << std::endl;
    auto d = *c;
    std::cout << "copy of c: " << d << std::endl;
    auto e = std::move(*c);
    delete c;
    std::cout << "moved c: " << e << std::endl;
}
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.