2013-07-18 8 views
11

EDYCJA: Zanim zaczniemy, to pytanie jest , a nie o poprawnym użyciu std::initializer_list; chodzi o to, co powinno zostać przekazane, gdy pożądana jest wygodna składnia. Dziękuję za pozostanie na temat.Przepuścić listę inicjalizacyjną lub kontener, patrząc w kierunku semantyki ruchu?


C++ 11 wprowadza std::initializer_list zdefiniować funkcje akceptujące usztywnione-startowe lista argumentów.

struct bar { 
    bar(char const *); 
    bar(int); 
} dog(42); 

fn foo(std::initializer_list<bar> args); 

foo({ "blah", 3, dog }); 

Składnia jest ładne, ale pod maską jest niesmaczne powodu różnych problemów:

  • Nie można sensownie przeniesiony. Powyższa funkcja musi skopiować dog z listy; nie można tego przekonwertować na budowę ruchową ani na elekcję. Typy typu "ruch" nie mogą być w ogóle używane. (No cóż, const_cast byłby prawdziwym obejściem. Jeśli jest jakiś artykuł na ten temat, chciałbym go zobaczyć.)

  • Nie ma też semantyki constexpr. (Pojawia się to w C++ 1j. Jest to jednak drobny problem.)

  • const nie rozprzestrzenia się tak jak wszędzie; initializer_list nigdy nie jest const, ale jej zawartość zawsze jest. (Ponieważ nie jest właścicielem jego zawartości, nie może dać prawa zapisu do kopii, ale kopiowanie jej w dowolne miejsce rzadko bywa bezpieczne.)

  • Obiekt initializer_list nie jest właścicielem jego pamięci (yikes); jego związek z całkowicie oddzielną, nagą tablicą (yikes), zapewniającą przechowywanie, jest określany w sposób trudny (yikes) jako relacja odniesienia do związanej tymczasowości (poczwórne yikes).

Wierzę te rzeczy zostaną ustalone w odpowiednim czasie, ale teraz jest tam najlepszych praktyk, aby uzyskać korzyści bez twarde kodowania initializer_list? Czy istnieje jakaś literatura lub analiza na temat bezpośredniej zależności od niej?

Oczywistym rozwiązaniem jest przekazanie według wartości standardowego pojemnika, takiego jak std::vector. Po skopiowaniu obiektów z poziomu obiektu initializer_list, jest on przesuwany, aby przejść przez wartość, a następnie można przenieść zawartość. Udoskonaleniem byłoby oferowanie przechowywania na stosie. Dobra biblioteka może być w stanie zaoferować większość zalet initializer_list, array i vector, nawet bez korzystania z pierwszego.

Wszelkie zasoby?

+2

[Czytałeś już to?] (Http://stackoverflow.com/questions/17623098/questions-regarding-the-design-of-stdinitializer-list/17623839#17623839) – Borgleader

+2

@Borgleader Wydaje mi się, że to omówiłem pytanie już. Wskazujesz coś szczególnego? – Potatoswatter

+4

Listy inicjatorów są tak zwane, ponieważ służą do * inicjowania rzeczy *. Jeśli nie inicjujesz czegoś z listą podobnych wartości, nie powinieneś przyjmować ich jako parametrów w swojej funkcji. Nie jest jasne, dlaczego 'foo' w ogóle bierze listę inicjalizującą. To nie jest pojemnik *; właśnie to jest ta odpowiedź, z którą się łączyłeś, próbując ci przypomnieć. Jeśli chcesz mieć tablicę, różni się ona od listy inicjalizatora. –

Odpowiedz

6

chodzi o to, co należy przekazać, gdy pożądana jest wygodna składnia.

Jeśli chcesz wygodę wielkości (np: użytkownik tylko typy na {} listę bez wywołań funkcji lub słowa), to musi akceptować wszelkie uprawnienia i ograniczenia właściwego initializer_list. Nawet jeśli spróbujesz przekonwertować go na coś innego, jak na jakąś formę array_ref, musisz między nimi mieć pośrednika initializer_list.Oznacza to, że nie możesz ominąć żadnego z problemów, na które natknąłeś się, np. Nie mogąc się z nich wyprowadzić.

Jeśli przechodzi przez initializer_list, musisz zaakceptować te ograniczenia. Dlatego alternatywą jest nie przechodzić przez initializer_list, co oznacza, że ​​będziesz musiał zaakceptować jakąś formę kontenera z określoną semantyką. Typ alternatywny musiałby być agregatem, aby konstrukcja alternatywnego obiektu nie napotkała na ten sam problem.

Prawdopodobnie zastanawiasz się, czy nie zmusić użytkownika do utworzenia std::array (lub tablicy języków) i podania go. Twoja funkcja może przybrać pewną formę klasy array_ref, która może być zbudowana z dowolnej tablicy o dowolnym rozmiarze, więc funkcja konsumująca nie jest ograniczona do jednego rozmiaru.

jednak tracisz wygodę rozmiar:

foo({ "blah", 3, dog }); 

Vs.

foo(std::array<bar, 3>{ "blah", 3, dog }); 

Jedynym sposobem na uniknięcie gadatliwość tutaj jest mieć foo wziąć std::array jako parametr. Co oznacza, że ​​może zająć tylko tablicę o określonym stałym rozmiarze. I nie można użyć C++ 14 zaproponowanych dynarray, ponieważ będzie to używać pośrednika initializer_list.

Ostatecznie nie powinieneś używać jednolitej składni inicjalizacji do przekazywania list wartości. Służy do inicjowania obiektów, a nie do przekazywania list rzeczy. std::initializer_list jest klasą, której jedynym celem jest użycie do zainicjowania określonego obiektu z arbitralnie długiej listy wartości identycznych typów. Ma on służyć jako obiekt pośredniczący między konstruktem językowym (listą z ustalonymi początkami) a konstruktorem, do którego mają zostać wprowadzone te wartości. Pozwala kompilatorowi wiedzieć, aby wywołać określonego konstruktora (konstruktora initializer_list), gdy zostanie mu podana dopasowana lista wartości ustawionych przez inited.

To jest cały powód, dla którego klasa istnieje.

Dlatego należy używać klasy wyłącznie w celu, dla którego została opracowana. Klasa istnieje, aby oznaczyć konstruktor jako pobierający listę wartości z listy usztywnionych-init. Powinieneś więc używać go tylko dla konstruktorów, którzy przyjmują taką wartość.

Jeśli masz jakąś funkcję foo, która działa jako pośrednik między jakimś typem wewnętrznym (którego nie chcesz bezpośrednio wystawiać) a listą wartości podaną przez użytkownika, musisz wziąć coś innego jako parametr do foo. Coś, co ma semantykę, której pragniesz, i które możesz następnie zasilić swoim wewnętrznym typem.

Ponadto, wydaje się, że masz błędne przekonanie o s i initializer_list s i ruchu. Nie można przenieść zinitializer_list, ale z pewnością można przenieść do jedno: będzie przenieść skonstruowane

foo({ "blah", 3, std::move(dog) }); 

Trzecia pozycja w wewnętrznej dog tablicy.

+0

@Potatoswatter: Naprawiono. –

+0

Myślę, że ważną kwestią tutaj jest * "Nie można przenieść się z części' initializer_list' "*. –

+0

@Potatoswatter jakikolwiek link do tego idiomu funkcji fabrycznej z listami inicjującymi? – TemplateRex

Powiązane problemy