Jeśli przekażę następujący kod przez moją migawkę GCC 4.7, spróbuje skopiować unique_ptr
s do wektora.
#include <vector>
#include <memory>
int main() {
using move_only = std::unique_ptr<int>;
std::vector<move_only> v { move_only(), move_only(), move_only() };
}
Oczywiście to nie może działać, ponieważ std::unique_ptr
nie można go skopiować:
błąd: użycie usuniętej funkcji '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] '
Czy GCC ma rację, próbując skopiować wskaźniki z listy inicjalizacyjnej?
c++
c++11
initializer-list
move-semantics
R. Martinho Fernandes
źródło
źródło
Odpowiedzi:
Streszczenie
<initializer_list>
w 18.9 jasno pokazuje, że elementy listy inicjalizującej są zawsze przekazywane przez stałą referencję. Niestety wydaje się, że nie ma sposobu na użycie semantyki ruchu w elementach listy inicjalizacyjnej w bieżącej wersji języka.W szczególności mamy:
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
źródło
const
, których nie można odrzucić w dobrze uformowanym programie.Edycja: Ponieważ @Johannes nie chce opublikować najlepszego rozwiązania jako odpowiedzi, po prostu to zrobię.
#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))}; }
Iteratory zwrócone przez
std::make_move_iterator
przeniesie wskazywany element podczas dereferencji.Oryginalna odpowiedź: użyjemy tutaj małego typu pomocnika:
#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;
Niestety, prosty kod tutaj nie zadziała:
std::vector<move_only> v{ rref(move_only()), rref(move_only()), rref(move_only()) };
Ponieważ standard z jakiegoś powodu nie definiuje konwertującego konstruktora kopiującego w ten sposób:
// in class initializer_list template<class U> initializer_list(initializer_list<U> const& other);
initializer_list<rref_wrapper<move_only>>
Stworzony przez Brace-init-listy ({...}
) nie będzie konwertować doinitializer_list<move_only>
, żevector<move_only>
trwa. Dlatego potrzebujemy tutaj dwustopniowej inicjalizacji: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());
źródło
std::ref
, nie? Może należy to nazwaćstd::rref
.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));
.move_iterator
jeszcze głowy .std::distance
forward lub lepszy” istd::move_iterator
dostosowują kategorię bazowego iteratora. Tak czy inaczej, dobre i zwięzłe rozwiązanie. Opublikuj to jako odpowiedź, może?Jak wspomniano w innych odpowiedziach, zachowanie
std::initializer_list
polega na trzymaniu przedmiotów według wartości i niedopuszczaniu do wyprowadzenia się, więc nie jest to możliwe. Oto jedno możliwe obejście, używając wywołania funkcji, w którym inicjatory są podawane jako argumenty wariadyczne:#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{}); }
Niestety
multi_emplace(foos, {});
kończy się niepowodzeniem, ponieważ nie można wydedukować typu for{}
, więc aby obiekty były konstruowane domyślnie, musisz powtórzyć nazwę klasy. (lub użyjvector::resize
)źródło
Korzystanie sztuczki Johannes Schaub z dnia
std::make_move_iterator()
zstd::experimental::make_array()
, można użyć funkcji pomocnika:#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 !! }
Zobacz to na żywo Coliru.
Być może ktoś może wykorzystać
std::make_array()
sztuczkę, aby pozwolićmake_vector()
zrobić to bezpośrednio, ale nie widziałem jak (dokładniej, spróbowałem tego, co moim zdaniem powinno zadziałać, nie udało mi się i poszedłem dalej). W każdym razie kompilator powinien być w stanie wstawić tablicę do transformacji wektorowej, tak jak robi to Clang z włączonym O2 GodBolt.źródło
Jak już wspomniano, nie jest możliwe zainicjowanie wektora typu tylko do przenoszenia za pomocą listy inicjalizującej. Rozwiązanie zaproponowane pierwotnie przez @Johannesa działa dobrze, ale mam inny pomysł ... Co jeśli nie utworzymy tymczasowej tablicy, a następnie przeniesiemy elementy stamtąd do wektora, ale użyjemy umieszczenia,
new
aby zainicjować tę tablicę już w miejscu blok pamięci wektora?Oto moja funkcja inicjująca wektor z
unique_ptr
przy użyciu pakietu argumentów:#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; } }
źródło
result.data()
nie jest wskaźnikiem do jakiejś pamięci losowej. Jest to wskaźnik do obiektu . Pomyśl, co dzieje się z tym biednym przedmiotem, kiedy umieszczasz na nim nowy.