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.