Neden bir "const" nesnesi üzerinde "std :: move" kullanabiliriz?


113

C ++ 11'de şu kodu yazabiliriz:

struct Cat {
   Cat(){}
};

const Cat cat;
std::move(cat); //this is valid in C++11

aradığımda std::move, nesneyi hareket ettirmek istediğim anlamına gelir, yani nesneyi değiştireceğim. Bir constnesneyi taşımak mantıksızdır, öyleyse neden std::movebu davranışı kısıtlamıyorsunuz? Gelecekte bir tuzak olacak, değil mi?

Burada, Brandon'ın yorumda bahsettiği tuzak şu anlama gelir:

"Sanırım bu onu sinsice" tuzağa düşürüyor "çünkü farkına varmazsa, amaçladığı gibi olmayan bir kopya ile sonuçlanıyor."

Scott Meyers'in 'Effective Modern C ++' kitabında bir örnek veriyor:

class Annotation {
public:
    explicit Annotation(const std::string text)
     : value(std::move(text)) //here we want to call string(string&&),
                              //but because text is const, 
                              //the return type of std::move(text) is const std::string&&
                              //so we actually called string(const string&)
                              //it is a bug which is very hard to find out
private:
    std::string value;
};

Eğer std::movebir faaliyet yasak edildi constnesne, kolayca doğru, hata öğrenebilir?


2
Ama hareket ettirmeye çalışın. Durumunu değiştirmeye çalışın. std::movekendi başına nesneye hiçbir şey yapmaz. Adının std::movekötü olduğu iddia edilebilir .
juanchopanza

3
Aslında hiçbir şeyi hareket ettirmiyor. Tek yaptığı bir rvalue referansına dönüştürmektir. Deneyin CAT cat2 = std::move(cat);, CATnormal hareket atamasını desteklediğini varsayarsak .
WhozCraig

11
std::movesadece bir kadro, aslında hiçbir şeyi hareket
Red Alert

2
@WhozCraig: Dikkatli olun, gönderdiğiniz kod herhangi bir uyarı olmadan derlenir ve yürütülür, bu da onu yanıltıcı hale getirir.
Mooing Duck

1
@MooingDuck derlenmeyeceğini asla söylemedi. yalnızca varsayılan copy-ctor etkinleştirildiği için çalışır. Susturun ve tekerlekler düşer.
WhozCraig

Yanıtlar:


55
struct strange {
  mutable size_t count = 0;
  strange( strange const&& o ):count(o.count) { o.count = 0; }
};

const strange s;
strange s2 = std::move(s);

Burada biz bir kullanımını bakın std::movebir üstünde T const. Bir T const&&. strangeBunun için tam olarak bu türü alan bir hareket oluşturucumuz var .

Ve denir.

Şimdi, bu garip türün, teklifinizin düzelteceği hatalardan daha nadir olduğu doğru.

Ancak diğer yandan, mevcut olan, üzerinde std::moveçalıştığınız türün a Tveya a olup olmadığını bilmediğiniz genel kodda daha iyi çalışır T const.


3
+1 , bir nesneyi neden aramak isteyeceğinizi gerçekten açıklamaya çalışan ilk cevap olduğu için . std::moveconst
Chris Drew

Bir işlev almayı göstermek için +1 const T&&. Bu, "bir rvalue-ref alacağım ama söz veriyorum onu ​​değiştirmeyeceğim" türünde bir "API protokolü" ifade eder. Sanırım, değişken kullanımdan ayrı olarak, bu nadirdir. Belki başka bir kullanım durumu, forward_as_tuplehemen hemen her şeyde kullanabilmek ve daha sonra kullanabilmektir.
F Pereira

107

Burada gözden kaçırdığınız bir numara var, yani bu std::move(cat) aslında hiçbir şeyi hareket ettirmiyor . Yalnızca derleyiciye hareket etmeyi denemesini söyler . Ancak, sınıfınız a'yı kabul eden bir kurucuya sahip olmadığından const CAT&&, bunun yerine örtük const CAT&kopya yapıcısını kullanacak ve güvenli bir şekilde kopyalayacaktır. Tehlike yok, tuzak yok. Kopya yapıcısı herhangi bir nedenle devre dışı bırakılırsa, bir derleyici hatası alırsınız.

struct CAT
{
   CAT(){}
   CAT(const CAT&) {std::cout << "COPY";}
   CAT(CAT&&) {std::cout << "MOVE";}
};

int main() {
    const CAT cat;
    CAT cat2 = std::move(cat);
}

baskılar COPY, değil MOVE.

http://coliru.stacked-crooked.com/a/0dff72133dbf9d1f

Bahsettiğiniz koddaki hatanın bir kararlılık sorunu değil , bir performans sorunu olduğunu unutmayın , bu nedenle böyle bir hata asla bir çökmeye neden olmaz. Yalnızca daha yavaş bir kopya kullanacaktır. Ek olarak, bu tür bir hata, hareket oluşturucuları olmayan const olmayan nesneler için de oluşur, bu nedenle yalnızca bir aşırı yükleme eklemek hepsini yakalayamaz. Yapıyı taşıma veya atamayı parametre türünden taşıma yeteneğini kontrol edebiliriz, ancak bu , kopya yapıcısına geri dönmesi beklenen genel şablon koduna müdahale eder. Ve heck, belki birisi inşa edebilmek istiyor , ben kimim ki yapamayacağını söyleyebilirim?constconst CAT&&


Upticked. Normal taşıma yapıcısının veya atama operatörünün kullanıcı tanımlı tanımı üzerine copy-ctor'un örtük olarak silinmesine dikkat etmek, bunu bozuk derleme yoluyla da gösterecektir. Güzel cevap.
WhozCraig

Ayrıca, değer olmayan bir değere ihtiyaç duyan bir kopya constkurucunun da yardımcı olmayacağından bahsetmeye değer . [class.copy] §8: "Aksi takdirde, örtük olarak beyan edilen kopya oluşturucu şu forma sahip olacaktır X::X(X&)"
Deduplicator

9
Bilgisayar / montaj terimleriyle "tuzak" demek istediğini sanmıyorum. Sanırım bu onu sinsi sinsice "tuzağa düşürüyor" çünkü farkına varmazsa, amaçladığı gibi olmayan bir kopya elde ediyor. Sanırım ..
Brandon

altın rozet karşılığında 1 ek oy ve 4 tane daha alabilir miyim? ;)
Yakk - Adam Nevraumont

Kod örneğinde çak bir beşlik. Konuyu gerçekten iyi anlıyor.
Brent Kod Yazıyor

20

Yanıtların geri kalanının şimdiye kadar gözden kaçırmasının bir nedeni, jenerik kodun hareket karşısında esnek olabilmesidir. Örneğin, aynı değerlere sahip başka bir tür kap oluşturmak için tüm öğeleri bir tür kaptan çıkaran genel bir işlev yazmak istediğimi varsayalım:

template <class C1, class C2>
C1
move_each(C2&& c2)
{
    return C1(std::make_move_iterator(c2.begin()),
              std::make_move_iterator(c2.end()));
}

Serin, şimdi nispeten verimli bir yaratabilir vector<string>bir gelen deque<string>ve her bireyin stringsürecinde taşınacaktır.

Ama ya a'dan geçmek istersem map?

int
main()
{
    std::map<int, std::string> m{{1, "one"}, {2, "two"}, {3, "three"}};
    auto v = move_each<std::vector<std::pair<int, std::string>>>(m);
    for (auto const& p : v)
        std::cout << "{" << p.first << ", " << p.second << "} ";
    std::cout << '\n';
}

Eğer std::movebir sivil ısrar constargüman, yukarıdaki instantiation move_eachbir uzaklaştırmaya çalışıyordu çünkü derlemek olmaz const int( key_typebir map). Ama bu kod umursamıyor o hareket edemez eğer key_type. Performans nedenleriyle mapped_type( std::string) ' i taşımak istiyor .

Bu örnek ve bunun gibi jenerik kodlamadaki sayısız başka örnek std::move, bir hareket etme talebi değil, bir hareket etme isteği .


2

OP ile aynı endişeye sahibim.

std :: move bir nesneyi hareket ettirmez, nesnenin taşınabilir olduğunu da garanti etmez. O halde neden buna hareket deniyor?

Bence taşınabilir olmamak şu iki senaryodan biri olabilir:

1. Hareketli tip sabittir.

Dilde const anahtar sözcüğüne sahip olmamızın nedeni, derleyicinin const olarak tanımlanan bir nesnede herhangi bir değişikliği önlemesini istememizdir. Scott Meyers'in kitabındaki örneğe bakıldığında:

    class Annotation {
    public:
     explicit Annotation(const std::string text)
     : value(std::move(text)) // "move" text into value; this code
     {  } // doesn't do what it seems to!    
     
    private:
     std::string value;
    };

Kelimenin tam anlamıyla ne anlama geliyor? Bir const dizesini değer üyesine taşıyın - en azından açıklamayı okumadan önce benim anlayışım bu.

Dil, std :: move () çağrıldığında hareket etmemek veya hareket etmeyi garanti etmemek niyetindeyse, kelime hareketini kullanırken kelimenin tam anlamıyla yanıltıcıdır.

Dil, std :: move kullanan insanları daha verimli olmaları için cesaretlendiriyorsa, bunun gibi tuzakları olabildiğince erken önlemelidir, özellikle bu tür bariz literal çelişkiler için.

İnsanların bir sabitin taşınmasının imkansız olduğunun farkında olması gerektiğine katılıyorum, ancak bu zorunluluk, bariz bir çelişki olduğunda derleyicinin sessiz kalabileceği anlamına gelmemelidir.

2. Nesnenin hareket yapıcısı yoktur

Şahsen, Chris Drew'un dediği gibi, bunun OP'nin endişesinden ayrı bir hikaye olduğunu düşünüyorum.

@hvd Bu bana biraz tartışmasız geliyor. Sırf OP'nin önerisinin dünyadaki tüm hataları düzeltmemesi, bunun kötü bir fikir olduğu anlamına gelmez (muhtemelen öyledir, ancak verdiğiniz nedenle değil). - Chris Drew


1

Bunun geriye dönük uyumluluk yönünden kimsenin bahsetmediğine şaşırdım. std::moveBilerek bunu C ++ 11'de yapmak için tasarlandığına inanıyorum . Büyük ölçüde C ++ 98 kitaplıklarına dayanan eski bir kod tabanıyla çalıştığınızı hayal edin, bu nedenle kopyalama atamasına geri dönüş olmadan taşıma işleri bozabilir.


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.