
R.Martinho Fernandes
Wenn ich den folgenden Code durch meinen GCC 4.7-Snapshot leite, versucht er, die unique_ptr
s 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?

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

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_ptr
verwendet 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;
}
}
9926400cookie-checkKann ich einen Vektor vom Typ „Nur verschieben“ listeninitialisieren?yes
Visual Studio und clang haben das gleiche Verhalten
– Jean-Simon Brochu
12. März 2018 um 15:53 Uhr