Frage Kann ich einen Vektor vom Nur-Bewegungstyp initialisieren?


Wenn ich den folgenden Code über meinen GCC 4.7 Snapshot übergebe, versuche ich den unique_ptrs in den Vektor.

#include <vector>
#include <memory>

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

Offensichtlich kann das nicht funktionieren, weil std::unique_ptr ist nicht kopierbar:

Fehler: Verwendung der gelöschten Funktion 'std :: unique_ptr <_Tp, _Dp> :: eindeutiges_ptr (const std :: eindeutiges_ptr <_Tp, _Dp> &) [mit _Tp = int; _Dp = std :: default_delete; std :: unique_ptr <_Tp, _Dp> = std :: eindeutige_ptr] '

Ist GCC korrekt beim Versuch, die Zeiger aus der Initialisierungsliste zu kopieren?


75
2017-12-12 00:49


Ursprung


Antworten:


Die Zusammenfassung von <initializer_list> in 18.9 macht es einigermaßen klar, dass Elemente einer Initialisiererliste immer über const-reference übergeben werden. Unglücklicherweise scheint es keine Möglichkeit zu geben, move-semantic in Initialisierungslistenelementen in der aktuellen Version der Sprache zu verwenden.

Insbesondere haben wir:

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

39
2017-12-12 00:55



Bearbeiten: Da @Johannes anscheinend nicht die beste Lösung als Antwort veröffentlichen möchte, mache ich es einfach.

#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))};
}

Die Iteratoren kamen zurück std::make_move_iterator wird das zugespitzte Element bewegen, wenn es dereferenziert wird.


Ursprüngliche Antwort: Wir verwenden hier einen kleinen Hilfstyp:

#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;

Leider funktioniert der direkte Code hier nicht:

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

Da der Standard, aus welchen Gründen auch immer, keinen Conversion-Copy-Konstruktor wie folgt definiert:

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

Das initializer_list<rref_wrapper<move_only>> erstellt von der Klammer-Init-Liste ({...}) wird nicht in die konvertiert initializer_list<move_only> dass die vector<move_only> dauert. Also brauchen wir hier eine zweistufige Initialisierung:

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());

51
2017-12-12 01:30



Wie in anderen Antworten erwähnt, das Verhalten von std::initializer_list ist es, Objekte nach Wert zu halten und nicht auszutreten, also ist das nicht möglich. Hier ist eine mögliche Problemumgehung, die einen Funktionsaufruf verwendet, bei dem die Initialisierer als Variadic-Argumente angegeben werden:

#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{});
}

Unglücklicherweise multi_emplace(foos, {}); schlägt fehl, da es den Typ für nicht ableiten kann {}Damit Objekte standardmäßig erstellt werden, müssen Sie den Klassennamen wiederholen. (oder benutzen vector::resize)


6
2017-11-10 01:03



Mit Johannes Schaubs Trick std::make_move_iterator() mit std::experimental::make_array()Sie können eine Hilfsfunktion verwenden:

#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 !!
}

Sieh es live an Coliru.

Vielleicht kann jemand etwas bewirken std::make_array()Tricks zu erlauben make_vector() um sein Ding direkt zu machen, aber ich habe nicht gesehen, wie (genauer gesagt, ich habe versucht, was ich dachte, sollte funktionieren, gescheitert, und ging weiter). In jedem Fall sollte der Compiler das Array in Vektortransformation einbinden können, wie Clang mit O2 on tut GodBolt.


0
2018-03-20 22:31



Wie bereits erwähnt wurde, ist es nicht möglich, einen Vektor vom Nur-Bewegungstyp mit einer Initialisierungsliste zu initialisieren. Die ursprünglich von @Johannes vorgeschlagene Lösung funktioniert gut, aber ich habe eine andere Idee ... Was ist, wenn wir kein temporäres Array erstellen und dann Elemente von dort in den Vektor verschieben, sondern die Platzierung verwenden? new um dieses Array bereits an Stelle des Speicherblocks des Vektors zu initialisieren?

Hier ist meine Funktion, einen Vektor von zu initialisieren unique_ptrbenutzt ein Argument-Pack:

#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;
    }
}

-1
2018-05-15 11:36