Bir vektör büyüdüğünde hareket semantiğini nasıl zorlayabilirim?


93

std::vectorBelli bir sınıftaki nesnelerim var A. Sınıf önemsiz değildir ve kopya oluşturuculara ve tanımlanmış taşıma yapıcılarına sahiptir.

std::vector<A>  myvec;

Vektörü Anesnelerle doldurursam (örneğin kullanarak myvec.push_back(a)), vektördeki A( const A&)öğelerin yeni kopyalarını somutlaştırmak için copy yapıcısını kullanarak vektörün boyutu büyür .

ABunun yerine sınıfın hareket oluşturucusunun kullanılmasını bir şekilde zorlayabilir miyim ?


5
Harekete duyarlı bir vektör uygulaması kullanarak bunu yapabilirsiniz.
— K-ballo

2
Bunu nasıl başaracağınıza biraz daha açık olabilir misiniz?
— Bertwim van Beest

1
Harekete duyarlı bir vektör uygulaması kullanırsınız. Görünüşe göre standart kitaplık uygulamanız (hangisi btw?) Harekete duyarlı değil. Boost'un hareketine duyarlı konteynerleri deneyebilirsiniz.
— K-ballo

1
Harekete duyarlı olan gcc 4.5.1 kullanıyorum.
— Bertwim van Beest

Benim kodumda, move yapıcısının açık "noexcept" e sahip olmamasına rağmen, kopya oluşturucuyu özel yapmak için çalıştı.
— Arne

Yanıtlar:


129

C ++ 'ya (özellikle std::vector) hareket oluşturucunuzun ve yıkıcınızın atmadığını noexcept,. Ardından, vektör büyüdüğünde hareket yapıcısı çağrılacaktır.

Aşağıdakiler tarafından saygı duyulan bir hareket oluşturucu nasıl bildirilir ve uygulanır std::vector:

A(A && rhs) noexcept { 
  std::cout << "i am the move constr" <<std::endl;
  ... some code doing the move ...  
  m_value=std::move(rhs.m_value) ; // etc...
}

Yapıcı değilse noexcept, std::vectoronu kullanamaz, çünkü o zaman standart tarafından talep edilen istisna garantilerini garanti edemez.

Standartta söylenenler hakkında daha fazla bilgi için, C ++ Taşıma semantiğini ve İstisnaları okuyun

İstisnalarla ilgisi olabileceğini ima eden Bo'ya kredi. Ayrıca Kerrek SB'nin tavsiyesini dikkate alın ve emplace_backmümkünse kullanın . Daha hızlı olabilir (ancak genellikle değildir), daha net ve daha kompakt olabilir, ancak bazı tuzaklar da vardır (özellikle açık olmayan kurucularda).

Düzenleyin , genellikle varsayılan istediğiniz şeydir: taşınabilen her şeyi taşıyın, geri kalanını kopyalayın. Bunu açıkça istemek için yazın

A(A && rhs) = default;

Bunu yaparak, mümkün olduğunda noexcept elde edeceksiniz: Varsayılan Move yapıcısı noexcept olarak mı tanımlanmış?

Visual Studio 2015 ve daha eski sürümlerin, taşıma anlamlarını desteklese bile bunu desteklemediğini unutmayın.


İlgi dışında, nasıl yok impl olmadığını "bilmek" value_type'in hareket ctor olduğunu noexcept? Belki de dil, çağıran kapsam da bir noexceptişlev olduğunda işlev çağrısı aday kümesini kısıtlar ?
— Orbit'te Hafiflik Yarışları

1
@LightnessRacesinOrbit Sadece en.cppreference.com/w/cpp/types/is_move_constructible gibi bir şey yaptığını varsayıyorum . Yalnızca bir hareket oluşturucu olabilir, bu nedenle bildirimle açıkça tanımlanmalıdır.
— Johan Lundberg

@LightnessRacesinOrbit, o zamandan beri bir noexcepthareket oluşturucu olup olmadığını gerçekten bilmenin (standart / kullanışlı) bir yolu olmadığını öğrendim . is_nothrow_move_constructiblebir nothrowkopya oluşturucu varsa doğru olacaktır . Pahalı nothrowkopya oluşturucuların gerçek durumunun farkında değilim, bu yüzden bunun gerçekten önemli olup olmadığı açık değil.
— Johan Lundberg

Benim için çalışmıyor. noexceptYıkıcım, taşıma yapıcım ve taşıma atama işlevlerimin tümü hem başlıkta hem de uygulamada işaretlenmiştir ve bir push_back (std:; taşıma) yaptığımda hala kopya yapıcısını çağırır. Burada saçımı yırtıyorum.
— AlastairG

1
@Johan sorunu buldum. Ben kullanıyordum std::move()yanlış üzerinde push_back()çağrı. Bir problem için o kadar sert baktığınız zamanlardan biri, önünüzdeki bariz hatayı görmüyorsunuz. Ve sonra öğle yemeği vaktiydi ve yorumumu silmeyi unuttum.
— AlastairG

17

İlginç bir şekilde, gcc 4.7.2'nin vektörü, hem hareket yapıcı hem de yıkıcı öyleyse yalnızca hareket oluşturucuyu kullanır noexcept. Basit bir örnek:

struct foo {
    foo() {}
    foo( const foo & ) noexcept { std::cout << "copy\n"; }
    foo( foo && ) noexcept { std::cout << "move\n"; }
    ~foo() noexcept {}
};

int main() {
    std::vector< foo > v;
    for ( int i = 0; i < 3; ++i ) v.emplace_back();
}

Bu, beklenen sonucu verir:

move
move
move

Ancak noexceptburadan kaldırdığımda ~foo()sonuç farklı oluyor:

copy
copy
copy

Sanırım bu da bu soruyu yanıtlıyor .


Bana öyle geliyor ki, diğer cevaplar haricinde yıkıcı olmak zorunda değil, sadece hareket kurucu hakkında konuşuyor.
— Nikola Benes

Öyle olmalı, ama anlaşılan gcc 4.7.2'de değildi. Yani bu problem aslında gcc'ye özgüdür. Yine de gcc 4.8.0'da düzeltilmelidir. İlgili yığın aşımı sorusuna bakın .
— Nikola Benes

-1

Görünüşe göre, (C ++ 17 ve öncesi için), std::vectoryeniden ayırmada hareket semantiğini kullanmaya zorlamanın tek yolu kopya yapıcısını silmek :). Bu şekilde, derleme zamanında hareket oluşturucularınızı kullanır veya denerken ölür :).

std::vectorYeniden tahsisatta move yapıcısını KULLANMAMASI GEREKEN birçok kural vardır , ancak onu nerede KULLANMASI GEREKTİĞİ konusunda hiçbir şey yoktur .

template<class T>
class move_only : public T{
public:
   move_only(){}
   move_only(const move_only&) = delete;
   move_only(move_only&&) noexcept {};
   ~move_only() noexcept {};

   using T::T;   
};

Canlı

veya

template<class T>
struct move_only{
   T value;

   template<class Arg, class ...Args, typename = std::enable_if_t<
            !std::is_same_v<move_only<T>&&, Arg >
            && !std::is_same_v<const move_only<T>&, Arg >
    >>
   move_only(Arg&& arg, Args&&... args)
      :value(std::forward<Arg>(arg), std::forward<Args>(args)...)
   {}

   move_only(){}
   move_only(const move_only&) = delete;   
   move_only(move_only&& other) noexcept : value(std::move(other.value)) {};    
   ~move_only() noexcept {};   
};

Canlı kod

Sizin Tsınıf olmalıdır noexcepthareket kurucu / ödev operatörü ve noexceptyıkıcı. Aksi takdirde derleme hatası alırsınız.

std::vector<move_only<MyClass>> vec;

1
Kopya oluşturucuyu silmek gerekli değildir. Move yapıcısı bir istisna değilse, kullanılacaktır.
— balki

@balki KULLANILABİLİR. Standart şimdi bunu GEREKTİRMEZ. Tartışma groups.google.com/a/isocpp.org/forum/…
— kule120
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.