Kann ich einen Vektor vom Typ „Nur verschieben“ listeninitialisieren?

Lesezeit: 9 Minuten

Kann ich einen Vektor vom Typ „Nur verschieben listeninitialisieren
R.Martinho Fernandes

Wenn ich den folgenden Code durch meinen GCC 4.7-Snapshot leite, versucht er, die 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>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete; std::unique_ptr<_Tp, _Dp> = std::unique_ptr]’

Ist GCC richtig, wenn er versucht, die Zeiger aus der Initialisierungsliste zu kopieren?

  • Visual Studio und clang haben das gleiche Verhalten

    – Jean-Simon Brochu

    12. März 2018 um 15:53 ​​Uhr

Kann ich einen Vektor vom Typ „Nur verschieben listeninitialisieren
Xeo

Bearbeiten: Da @Johannes anscheinend nicht die beste Lösung als Antwort posten 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 von zurückgegebenen Iteratoren std::make_move_iterator verschiebt das Element, auf das gezeigt wird, wenn es dereferenziert wird.


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

#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 einfache 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 konvertierenden Kopierkonstruktor wie folgt definiert:

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

Die initializer_list<rref_wrapper<move_only>> erstellt durch die Klammer-Init-Liste ({...}) wird nicht in konvertiert initializer_list<move_only> dass die vector<move_only> nimmt. Wir brauchen hier also 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());

  • Ah … das ist das rvalue-Analogon von std::ref, nicht? Vielleicht sollte es heißen std::rref.

    – Kerrek SB

    12. Dezember 2011 um 1:39 Uhr

  • Nun, ich denke, das sollte nicht ohne Erwähnung in einem Kommentar bleiben 🙂 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 – litb

    12. Dezember 2011 um 22:18 Uhr

  • @Johannes: Manchmal sind es die einfachen Lösungen, die mir einfach entgehen. Obwohl ich zugeben muss, dass ich mich nicht darum gekümmert habe move_iteratorist noch.

    – Xeo

    12. Dezember 2011 um 22:37 Uhr

  • @Johannes: Warum ist das keine Antwort? 🙂

    – Xeo

    12. Dezember 2011 um 22:43 Uhr

  • @JohanLundberg: Ich würde das als QoI-Problem betrachten, aber ich verstehe nicht, warum es das nicht tun könnte. Die stdlib von VC++ zum Beispiel Tag-Dispatches basierend auf der Iterator-Kategorie und -Verwendung std::distance für Forward-or-better-Iteratoren und std::move_iterator passt die Kategorie des zugrunde liegenden Iterators an. Wie auch immer, gute und prägnante Lösung. Vielleicht als Antwort posten?

    – Xeo

    2. November 2012 um 22:49 Uhr


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

Konkret 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

  • Betrachten Sie das Idiom in, das auf cpptruths (cpptruths.blogspot.com/2013/09/…). Die Idee ist, lvalue/rvalue zur Laufzeit zu bestimmen und dann move oder copy-construction aufzurufen. in erkennt rvalue/lvalue, obwohl die von initializer_list bereitgestellte Standardschnittstelle eine const-Referenz ist.

    – Sumant

    24. September 2013 um 19:34 Uhr


  • @Sumant Kommt mir nicht so “idiomatisch” vor: Ist es nicht stattdessen reines UB? wie es nicht nur der Iterator, sondern auch die zugrunde liegenden Elemente selbst sein könnten constdie in einem wohlgeformten Programm nicht weggeworfen werden können.

    – Unterstrich_d

    18. Juli 2016 um 10:49 Uhr

Wie in anderen Antworten erwähnt, ist das Verhalten von std::initializer_list Ist es, Objekte nach Wert zu halten und nicht ausziehen zu lassen, so ist dies nicht möglich. Hier ist eine mögliche Problemumgehung mit einem Funktionsaufruf, bei dem die Initialisierer als variadische 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 der Typ für nicht abgeleitet werden kann {}, also müssen Sie für Objekte, die standardmäßig erstellt werden sollen, den Klassennamen wiederholen. (oder verwenden vector::resize)

  • Die rekursive Paketerweiterung könnte durch den Dummy-Array-Kommaoperator hack ersetzt werden, um ein paar Codezeilen einzusparen

    – MM

    10. November 2015 um 1:12 Uhr

1647058208 913 Kann ich einen Vektor vom Typ „Nur verschieben listeninitialisieren
Metall

Update für C++20: Mit dem Trick von Johannes Schaub std::make_move_iterator() mit C++20 std::to_array()können Sie eine Hilfsfunktion wie unto verwenden make_tuple() usw., hier genannt make_vector():

#include <array>
#include <memory>
#include <vector>

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 )
{
    return make_vector( std::to_array({ std::forward<T>
}

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

Sehen Sie es live weiter Gottbolzen.


Ähnliche Antwort für älteres C++:

Mit dem Trick von Johannes Schaub std::make_move_iterator() mit std::experimental::make_array()können Sie 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>
}

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

Sehen Sie es live weiter Coliru.

Vielleicht kann jemand hebeln std::make_array()‘s Trickserei zuzulassen make_vector() sein Ding direkt zu machen, aber ich sah nicht wie (genauer gesagt, ich versuchte, was meiner Meinung nach funktionieren sollte, scheiterte und ging weiter). In jedem Fall sollte der Compiler in der Lage sein, die Array-zu-Vektor-Transformation zu inlinen, wie es Clang mit O2 tut GodBolt.

Wie bereits erwähnt, ist es nicht möglich, einen Vektor vom Nur-Verschieben-Typ mit einer Initialisiererliste 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 dieses Array bereits anstelle des Speicherblocks des Vektors zu initialisieren?

Hier ist meine Funktion zum Initialisieren eines Vektors von unique_ptrverwendet ein Argumentpaket:

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

  • Das ist eine schreckliche Idee. Placement new ist kein Hammer, es ist ein Werkzeug feiner Präzision. result.data() ist kein Zeiger auf irgendeinen Zufallsspeicher. Es ist ein Zeiger auf ein Objekt. Denken Sie daran, was mit diesem armen Objekt passiert, wenn Sie es neu platzieren.

    – R.Martinho Fernandes

    15. Mai 2013 um 11:38 Uhr


  • Außerdem ist die Array-Form der Platzierung neu nicht wirklich verwendbar. stackoverflow.com/questions/8720425/…

    – R.Martinho Fernandes

    15. Mai 2013 um 11:42 Uhr

  • @R. Martinho Fernandes: Danke für den Hinweis, dass die Platzierung neu für Arrays nicht funktionieren würde. Jetzt verstehe ich, warum das eine schlechte Idee war.

    – Gart

    15. Mai 2013 um 11:55 Uhr

  • Das ist eine schreckliche Idee. Placement new ist kein Hammer, es ist ein Werkzeug feiner Präzision. result.data() ist kein Zeiger auf irgendeinen Zufallsspeicher. Es ist ein Zeiger auf ein Objekt. Denken Sie daran, was mit diesem armen Objekt passiert, wenn Sie es neu platzieren.

    – R.Martinho Fernandes

    15. Mai 2013 um 11:38 Uhr


  • Außerdem ist die Array-Form der Platzierung neu nicht wirklich verwendbar. stackoverflow.com/questions/8720425/…

    – R.Martinho Fernandes

    15. Mai 2013 um 11:42 Uhr

  • @R. Martinho Fernandes: Danke für den Hinweis, dass die Platzierung neu für Arrays nicht funktionieren würde. Jetzt verstehe ich, warum das eine schlechte Idee war.

    – Gart

    15. Mai 2013 um 11:55 Uhr

992640cookie-checkKann ich einen Vektor vom Typ „Nur verschieben“ listeninitialisieren?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy