2016-04-02 25 views
10

Powiedzmy masz zmienną typu std::vector<std::string> i zainicjować go z listy inicjatora:Jak przenieść elementy pliku initializer_list?

using V = std::vector<std::string>; 
V v = { "Hello", "little", "world", "of", "move", "semantics" }; 

Kompilator stworzy tymczasowy std::string dla każdej struny dosłownym, utworzyć listę initializer na nich, a następnie wywołać konstruktor dla V i utwórz wektor. Ctor nie wie, że wszystkie te ciągi są tymczasowe, więc jest to każdy ciąg znaków.

Nie znalazłem niczego w standardzie, który pozwala ctorowi wektorowemu przesuwać elementy, gdy są tymczasowe.

Czy czegoś brakuje lub czy korzystanie z list inicjalizujących prowadzi do niepotrzebnych kopii? Piszę klasy, w których ten problem może prowadzić do znacznie nieefektywnego kodu. Każda technika unikania niepotrzebnych kopii byłaby bardzo doceniana.

+0

Dobre pytanie. Nie sądzę, że listy inicjalizatorów były pierwotnie * przeznaczone * do robienia czegokolwiek, ale przekazywały sekwencję plików tymczasowych. Ale teraz możesz zadeklarować zmienną listy inicjalizatorów. Nie byłoby to możliwe, gdyby wektor wysysał wnętrzności z każdego przedmiotu na tak długiej liście. Ale czekaj, konstruktor z wartością rvalue ref do listy inicjalizatora. Może? –

+0

@ Cheersandhth.-Alf Myślę, że mogę przeciążać na listy inicjalizatora przez const-ref, non const-ref i rvalue-ref. Mogę następnie ręcznie przenieść elementy z listy inicjalizującej rvalue-ref. Nie jest elegancka i nie działa z kontenerami STL, z których obecnie korzystam jako pamięci bazowej (wektor i mapa), ale lepiej niż nic. Dzięki. –

+0

duplikat [inicjalizator \ _list i semantyka ruchu] (https://stackoverflow.com/questions/8193102/initializer-list-and-move-semantics) –

Odpowiedz

4

Nie ma sposobu, aby zapobiec kopiowaniu z initializer_list<string>, ponieważ standard określa wywołanie konstruktora biorąc listę initializer argument z nawiasów klamrowych inicjatora jako rzeczywisty argument następująco (podkreślenie dodane):

C++ 14 §8.5.4/5

obiektu typu std::initializer_list<E> jest wykonana z listy inicjatora jak implementacja przypisane tymczasowe tablicę N elementów typu const E, gdzie N jest liczba elementów na liście inicjatora

IMHO to jest naprawdę niefortunne.

Sposób obejścia (dla własnych zajęć) to zaakceptowanie initializer_list<char const*>.


Oto przykład z obejścia stosowanej do std::vector<string>. W tym przypadku, gdy nie kontrolujesz kodu klasy, oznacza to jawne deklarowanie tablicy danych (faktycznie initializer_list). To jest tak jak z C++ 03, którego mechanizm lista initializer miała unikać:

#include <vector> 
#include <initializer_list> 
#include <iostream> 
#include <iterator>    // std::begin, std::end 
using namespace std; 

struct My_string 
{ 
    char const* const ps; 

    My_string(char const* const s) 
     : ps(s) 
    { 
     cout << " My_string(*) <- '" << s << "'" << endl; 
    } 

    My_string(My_string const& other) 
     : ps(other.ps) 
    { 
     cout << " My_string(const&) <- '" << other.ps << "'" << endl; 
    }; 

    My_string(My_string&& other) 
     : ps(other.ps) 
    { 
     cout << " My_string(&&) <- '" << other.ps << "'" << endl; 
    }; 
}; 

auto main() -> int 
{ 
    cout << "Making vector a." << endl; 
    vector<My_string> const a = {"a1", "a2", "a3"}; 
    cout << "Making data for vector b." << endl; 
    auto const b_data   = { "b1", "b2", "b3" }; 
    cout << "Making vector b." << endl; 
    vector<My_string> const b(begin(b_data), end(b_data)); 
} 

wyjściowa:

 
Making vector a. 
    My_string(*) <- 'a1' 
    My_string(*) <- 'a2' 
    My_string(*) <- 'a3' 
    My_string(const&) <- 'a1' 
    My_string(const&) <- 'a2' 
    My_string(const&) <- 'a3' 
Making data for vector b. 
Making vector b. 
    My_string(*) <- 'b1' 
    My_string(*) <- 'b2' 
    My_string(*) <- 'b3' 
+0

Możesz także użyć konstruktora szablonu variadic. – o11c

+0

'const E'? Dziękuję * naprawdę * niefortunne. Czy jest jakikolwiek powód? W przeciwnym razie wykrycie listy inicjalizacyjnej rvalue pozwoliłoby mi przynajmniej ręcznie przenieść się, ale z 'const E', to wydaje się nielegalne !? (I tak naprawdę nie używam 'std :: string', używam typu, który napisałem, który może być droższy do skopiowania) –

+0

Myślę, że znalazłem rozwiązanie, używając' 'mutable', dodałem samo-odpowiedź. Co o tym myślisz? –

2

Po pewnym myślenia, wymyśliłem rozwiązanie oparte na mutable. Druga odpowiedź jest nadal w większości poprawna, ale można utworzyć proxy ze zmiennym elementem, aby pozbyć się najwyższego poziomu, a następnie przenieść elementy stamtąd. Metody przyjmujące listę inicjalizacyjną powinny więc przeciążać listę inicjalizującą const-ref i wersję rvalue-ref, aby wiedzieć, kiedy mogą się poruszać.

Oto działający przykład, może początkowo wyglądać dowolnie, ale w moim praktycznym przypadku, rozwiązał problem.

#include <iostream> 
#include <vector> 

// to show which operations are called 
struct my_string 
{ 
    const char* s_; 
    my_string(const char* s) : s_(s) { std::cout << "my_string(const char*) " << s_ << std::endl; } 
    my_string(const my_string& m) : s_(m.s_) { std::cout << "my_string(const my_string&) " << s_ << std::endl; } 
    my_string(my_string&& m) noexcept : s_(m.s_) { std::cout << "my_string(my_string&&) " << s_ << std::endl; } 
    ~my_string() { std::cout << "~my_string() " << s_ << std::endl; } 
}; 

// the proxy 
struct my_string_proxy 
{ 
    mutable my_string s_; 

    // add all ctors needed to initialize my_string 
    my_string_proxy(const char* s) : s_(s) {} 
}; 

// functions/methods should be overloaded 
// for the initializer list versions 

void insert(std::vector<my_string>& v, const std::initializer_list<my_string_proxy>& il) 
{ 
    for(auto& e : il) { 
     v.push_back(e.s_); 
    } 
} 

void insert(std::vector<my_string>& v, std::initializer_list<my_string_proxy>&& il) 
{ 
    for(auto& e : il) { 
     v.push_back(std::move(e.s_)); 
    } 
} 

int main() 
{ 
    std::vector<my_string> words; 
    insert(words, { {"Hello"}, {"initializer"}, {"with"}, {"move"}, {"support"} }); 
} 

Live example