Yalnızca hareket tipi bir vektörü listeleyebilir miyim?


96

Aşağıdaki kodu GCC 4.7 anlık görüntümden geçirirsem, s'leri unique_ptrvektöre kopyalamaya çalışır .

#include <vector>
#include <memory>

int main() {
    using move_only = std::unique_ptr<int>;
    std::vector<move_only> v { move_only(), move_only(), move_only() };
}

Açıkçası bu işe yaramaz çünkü std::unique_ptrkopyalanamaz:

hata: silinmiş işlevin kullanımı 'std :: unique_ptr <_Tp, _Dp> :: unique_ptr (const std :: unique_ptr <_Tp, _Dp> &) [_Tp = int; _Dp = std :: default_delete; std :: unique_ptr <_Tp, _Dp> = std :: unique_ptr] '

GCC, işaretçileri başlatıcı listesinden kopyalamaya çalışırken doğru mu?


Visual Studio ve clang aynı davranışa sahip
Jean-Simon Brochu

Yanıtlar:


47

18.9'daki özet, <initializer_list>bir başlatıcı listesinin öğelerinin her zaman const-reference yoluyla geçirildiğini makul ölçüde açık hale getirir. Ne yazık ki, dilin mevcut revizyonunda başlatıcı liste öğelerinde hareket-anlambilimini kullanmanın herhangi bir yolu görünmüyor.

Özellikle şunlara sahibiz:

typedef const E& reference;
typedef const E& const_reference;

typedef const E* iterator;
typedef const E* const_iterator;

const E* begin() const noexcept; // first element
const E* end() const noexcept; // one past the last element

4
Cpptruths'da ( cpptruths.blogspot.com/2013/09/… ) açıklanan in <T> deyimini düşünün . Buradaki fikir, çalışma zamanında lvalue / rvalue'yu belirlemek ve ardından taşıma veya kopyalama-oluşturma çağrısı yapmaktır. <T> 'de, initializer_list tarafından sağlanan standart arayüz sabit referans olmasına rağmen rvalue / lvalue'yu algılar.
Sumant

3
@Sumant "Bana deyimsel" görünmüyor: onun yerine saf UB değil mi? sadece yineleyici değil, temelde yatan unsurların kendilerinin olabileceği gibi const, iyi biçimlendirilmiş bir programda bir kenara atılamaz.
underscore_d

66

Düzenleme: @Johannes en iyi çözümü bir cevap olarak göndermek istemiyor gibi göründüğü için, sadece yapacağım.

#include <iterator>
#include <vector>
#include <memory>

int main(){
  using move_only = std::unique_ptr<int>;
  move_only init[] = { move_only(), move_only(), move_only() };
  std::vector<move_only> v{std::make_move_iterator(std::begin(init)),
      std::make_move_iterator(std::end(init))};
}

Tarafından döndürülen yineleyiciler std::make_move_iterator, referans alınırken işaret edilen öğeyi hareket ettirir.


Orijinal cevap: Burada küçük bir yardımcı türü kullanacağız:

#include <utility>
#include <type_traits>

template<class T>
struct rref_wrapper
{ // CAUTION - very volatile, use with care
  explicit rref_wrapper(T&& v)
    : _val(std::move(v)) {}

  explicit operator T() const{
    return T{ std::move(_val) };
  }

private:
  T&& _val;
};

// only usable on temporaries
template<class T>
typename std::enable_if<
  !std::is_lvalue_reference<T>::value,
  rref_wrapper<T>
>::type rref(T&& v){
  return rref_wrapper<T>(std::move(v));
}

// lvalue reference can go away
template<class T>
void rref(T&) = delete;

Ne yazık ki, buradaki basit kod işe yaramayacak:

std::vector<move_only> v{ rref(move_only()), rref(move_only()), rref(move_only()) };

Standart, her ne sebeple olursa olsun, böyle bir dönüştürücü kopya oluşturucuyu tanımlamadığından:

// in class initializer_list
template<class U>
initializer_list(initializer_list<U> const& other);

initializer_list<rref_wrapper<move_only>>Ayracı-init-listesinde (yarattığı {...}) dönüştürmek olmaz initializer_list<move_only>o vector<move_only>alır. Bu yüzden burada iki aşamalı bir başlatmaya ihtiyacımız var:

std::initializer_list<rref_wrapper<move_only>> il{ rref(move_only()),
                                                   rref(move_only()),
                                                   rref(move_only()) };
std::vector<move_only> v(il.begin(), il.end());

1
Ah ... bu rvalue analogu std::ref, non? Belki aranmalı std::rref.
Kerrek SB

19
Şimdi, sanırım bu bir yorumda belirtilmeden bırakılmamalı :) move_only m[] = { move_only(), move_only(), move_only() }; std::vector<move_only> v(std::make_move_iterator(m), std::make_move_iterator(m + 3));.
Johannes Schaub -

1
@Johannes: Bazen benden kaçan basit çözümler oluyor. İtiraf etmeliyim ki, henüz onlarla uğraşmadım move_iterator.
Xeo

2
@Johannes: Ayrıca, bu neden bir cevap değil? :)
Xeo

1
@JohanLundberg: Bunun bir QoI sorunu olduğunu düşünürdüm, ancak neden bunu yapamadığını anlamıyorum. VC ++ 'nın stdlib'i, örneğin yineleyici kategorisine dayalı etiket gönderimi yapar ve std::distanceileri veya daha iyi yineleyiciler için kullanır std::move_iteratorve temel yineleyicinin kategorisini uyarlar. Her neyse, iyi ve özlü bir çözüm. Cevap olarak gönderebilir misin?
Xeo

10

Diğer cevaplarda belirtildiği gibi, davranışı std::initializer_listnesneleri değerine göre tutmak ve taşınmaya izin vermemek, dolayısıyla bu mümkün değildir. Başlatıcıların değişken bağımsız değişkenler olarak verildiği bir işlev çağrısı kullanan olası bir geçici çözüm aşağıda verilmiştir:

#include <vector>
#include <memory>

struct Foo
{
    std::unique_ptr<int> u;
    int x;
    Foo(int x = 0): x(x) {}
};

template<typename V>        // recursion-ender
void multi_emplace(std::vector<V> &vec) {}

template<typename V, typename T1, typename... Types>
void multi_emplace(std::vector<V> &vec, T1&& t1, Types&&... args)
{
    vec.emplace_back( std::move(t1) );
    multi_emplace(vec, args...);
}

int main()
{
    std::vector<Foo> foos;
    multi_emplace(foos, 1, 2, 3, 4, 5);
    multi_emplace(foos, Foo{}, Foo{});
}

Ne yazık ki multi_emplace(foos, {});, türünü çıkaramadığı için başarısız olur, bu {}nedenle nesnelerin varsayılan olarak yapılandırılması için sınıf adını tekrarlamanız gerekir. (veya kullanın vector::resize)


4
Yinelemeli paket genişletmesi, birkaç satır koddan tasarruf etmek için kukla dizi virgül operatörü hackiyle değiştirilebilir
MM

0

Johannes Schaub'un yaptığı hile kullanılması std::make_move_iterator()ile std::experimental::make_array(), bir yardımcı işlevi kullanabilirsiniz:

#include <memory>
#include <type_traits>
#include <vector>
#include <experimental/array>

struct X {};

template<class T, std::size_t N>
auto make_vector( std::array<T,N>&& a )
    -> std::vector<T>
{
    return { std::make_move_iterator(std::begin(a)), std::make_move_iterator(std::end(a)) };
}

template<class... T>
auto make_vector( T&& ... t )
    -> std::vector<typename std::common_type<T...>::type>
{
    return make_vector( std::experimental::make_array( std::forward<T>(t)... ) );
}

int main()
{
    using UX = std::unique_ptr<X>;
    const auto a  = std::experimental::make_array( UX{}, UX{}, UX{} ); // Ok
    const auto v0 = make_vector( UX{}, UX{}, UX{} );                   // Ok
    //const auto v1 = std::vector< UX >{ UX{}, UX{}, UX{} };           // !! Error !!
}

Canlı görün Coliru.

Belki birisi işini doğrudan yapmasına std::make_array()izin make_vector()vermek için onun hilelerinden yararlanabilir , ancak nasıl olduğunu görmedim (daha doğrusu, işe yaraması gerektiğini düşündüğüm şeyi denedim, başarısız oldum ve devam ettim). Her durumda, derleyici, Clang'ın O2 açıkken yaptığı gibi, diziyi vektör dönüşümüne satır içi yapabilmelidir GodBolt.


-1

Daha önce de belirtildiği gibi, yalnızca hareket tipi bir vektörü bir başlatıcı listesiyle başlatmak mümkün değildir. @Johannes tarafından önerilen çözüm işe yarıyor, ancak başka bir fikrim var ... Ya geçici bir dizi oluşturmazsak ve daha sonra öğeleri oradan vektöre taşımazsak, ancak newbu diziyi zaten yerine yerleştirmek için yerleşimi kullanırsak vector'un hafıza bloğu?

unique_ptrArgüman paketi kullanarak bir vektörü başlatmak için benim fonksiyonum :

#include <iostream>
#include <vector>
#include <make_unique.h>  /// @see http://stackoverflow.com/questions/7038357/make-unique-and-perfect-forwarding

template <typename T, typename... Items>
inline std::vector<std::unique_ptr<T>> make_vector_of_unique(Items&&... items) {
    typedef std::unique_ptr<T> value_type;

    // Allocate memory for all items
    std::vector<value_type> result(sizeof...(Items));

    // Initialize the array in place of allocated memory
    new (result.data()) value_type[sizeof...(Items)] {
        make_unique<typename std::remove_reference<Items>::type>(std::forward<Items>(items))...
    };
    return result;
}

int main(int, char**)
{
    auto testVector = make_vector_of_unique<int>(1,2,3);
    for (auto const &item : testVector) {
        std::cout << *item << std::endl;
    }
}

Bu berbat bir fikir. Yeni yerleştirme bir çekiç değil, hassas bir alettir. result.data()rastgele bir hafızanın göstergesi değildir. Bir nesneye göstericidir . Üzerine yeni yerleştirdiğinizde o zavallı nesneye ne olduğunu düşünün.
R. Martinho Fernandes

Ayrıca, yerleşim yeni dizi biçimi kullanılabilir gerçekten değil stackoverflow.com/questions/8720425/...
R. Martinho Fernandes

@R. Martinho Fernandes: Diziler için yeni yerleştirmenin işe yaramayacağını belirttiğiniz için teşekkürler. Şimdi bunun neden kötü bir fikir olduğunu anlıyorum.
Gart
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.