garlonicon Temat założony przez niniejszego użytkownika |
Wypełnianie struktury na liście inicjalizacyjnej konstruktora » 2019-01-30 04:19:04 Mamy następujący kod: struct Foobar { int foo; int bar; };
class Wrapper { Foobar foobar; int number; public: Wrapper( int foo, int bar, int number ); };
Wrapper::Wrapper( int foo, int bar, int number ) : number( number ) { foobar.foo = foo; foobar.bar = bar; }
int main() { Wrapper wrapper( 1, 2, 3 ); } Kompiluje się i działa. Załóżmy, że z jakiegoś powodu jednak chcemy przenieść wypełnianie struktury Foobar do listy inicjalizacyjnej konstruktora. Wtedy konstruktor klasy Wrapper może wyglądać jakoś tak: Wrapper::Wrapper( int foo, int bar, int number ) : foobar { foo, bar }, number( number ) { } Czy istnieje jakakolwiek składnia pozwalająca na oddzielne wypełnianie każdego z pól struktury Foobar osobno, poprzez nazwę? Chodzi o kod w stylu: Wrapper::Wrapper( int foo, int bar, int number ) :( foobar.foo )( foo ) ,( foobar.bar )( bar ) , number( number ) { } To już z kolei nie działa. Kompilacja g++ main.cpp daje błędy: main.cpp: In constructor ‘Wrapper::Wrapper(int, int, int)’: main.cpp:17:5: error: anachronistic old-style base class initializer [-fpermissive] (foobar.foo)(foo), ^ main.cpp:16:1: error: unnamed initializer for ‘Wrapper’, which has no base classes : ^ main.cpp:17:17: error: expected ‘{’ before ‘(’ token (foobar.foo)(foo), ^ main.cpp: At global scope: main.cpp:17:22: error: expected constructor, destructor, or type conversion before ‘,’ token (foobar.foo)(foo), ^ main.cpp:18:12: error: expected ‘)’ before ‘.’ token (foobar.bar)(bar), ~ ^ ) Zakładamy, że sama struktura Foobar jest częścią zewnętrznej biblioteki, której nie możemy zmieniać. |
|
YooSy |
» 2019-01-30 08:43:44 Wrapper::Wrapper( int foo, int bar, int number ) :( foobar.foo )( foo ) ,( foobar.bar )( bar ) , number( number ) { }
|
Aby można było przypisywać wartość do poszczególnych pól obiektu, musi on istnieć (czyli w ciele konstruktora). W tym przypadku jeszcze nie jest stworzony, bo w liście inicjalizacyjnej uruchamiane są konstruktory pól składowych klasy. Czy istnieje jakakolwiek składnia pozwalająca na oddzielne wypełnianie każdego z pól struktury |
Zdradzisz trochę więcej szczegółów, po co taki zabieg? Jakby nie było, w obu przypadkach musisz podać wartości pól składowych foobar. Dodaj może jakiś setter do ustawiania pól. |
|
Monika90 |
» 2019-01-30 10:35:55 C++20 ma tak zwane designated initializers, a wygląda to tak: Wrapper::Wrapper( int foo, int bar, int number ) : foobar {.foo = foo,.bar = bar }, number( number ) { }
W GCC 8.1.0 już działa. |
|
garlonicon Temat założony przez niniejszego użytkownika |
» 2019-01-30 15:45:55 Zdradzisz trochę więcej szczegółów, po co taki zabieg? |
Przenośność. Zgodnie ze standardem kompilator ma prawo dowolnie zmieniać kolejność pól w strukturze, zachowując ten sam poziom dostępu (czyli jeśli dwa pola są publiczne, to mogą być zamienione; jeśli jedno z nich jest prywatne, a drugie publiczne, to już nie). Dlatego foobar{foo,bar} może w jakimś szczególnym przypadku zadziałać niezgodnie z oczekiwaniami. Zakładając jednak, że kompilator nie rusza kolejności pól, to pozostaje jeszcze kwestia nowszych wersji biblioteki. Załóżmy, że autor w pewnym momencie postanowi posortować pola tak, aby uzyskać jak najmniejszy rozmiar (ze względu na wyrównanie), a całą resztę ustawi alfabetycznie. Wtedy w nowszej wersji biblioteki taka struktura może wyglądać tak: struct Foobar { int bar; int foo; }; W takim przypadku foobar{foo,bar} może okazać się błędne, a skoro tak, to dobrze byłoby zapisać to w taki sposób, aby przy takich zmianach w takim kodzie dostać co najmniej warninga (lub jednoznacznie odwoływać się do każdego pola po nazwie i mieć kod odporny na takie drobne zmiany). Dodaj może jakiś setter do ustawiania pól. |
Jak wywołać taki setter na liście inicjalizacyjnej konstruktora? Na razie mam coś takiego: class Wrapper { Foobar foobar; int number; Foobar createFoobar( int foo, int bar ); public: Wrapper( int foo, int bar, int number ); };
Foobar Wrapper::createFoobar( int foo, int bar ) { Foobar result; result.foo = foo; result.bar = bar; return result; }
Wrapper::Wrapper( int foo, int bar, int number ) : foobar( createFoobar( foo, bar ) ) , number( number ) { } Nie wiem, jak będzie z wydajnością, ale to chyba podpada pod copy-elision lub coś w tym stylu, więc pewnie wewnątrz createFoobar() mogę bezpiecznie zwracać taki obiekt przez wartość. C++20 ma tak zwane designated initializers |
Dziękuję, działa. Jednak rozumiem, że to jest rozwiązanie tymczasowe i może nie wejść do nowego standardu? kolejność inicjaliztorów musi być taka sama jak kolejność deklaracji |
Czyli nie da się napisać tego tak, aby odwołać się po nazwie i całkowicie uniezależnić od kolejności pól w strukturze? A w razie gdyby kompilator lub autor biblioteki zechciał te pola zamienić, to dostanę zawsze błąd? |
|
pekfos |
» 2019-01-30 16:01:38 Zdradzisz trochę więcej szczegółów, po co taki zabieg? | Przenośność. Zgodnie ze standardem kompilator ma prawo dowolnie zmieniać kolejność pól w strukturze, zachowując ten sam poziom dostępu (czyli jeśli dwa pola są publiczne, to mogą być zamienione; jeśli jedno z nich jest prywatne, a drugie publiczne, to już nie). Dlatego foobar{foo,bar} może w jakimś szczególnym przypadku zadziałać niezgodnie z oczekiwaniami. |
Non-static data members of a (non-union) class with the same access control are allocated so that later members have higher addresses within a class object. The order of allocation of non-static data members with different access control is unspecified. | http://eel.is/c++draft/class.mem#19Poza tym.. Kolejność inicjalizacji jest brana z kolejności pól w definicji klasy, a nie układu w pamięci, jaki wyszedł kompilatorowi. |
|
garlonicon Temat założony przez niniejszego użytkownika |
» 2019-01-30 16:08:12 Kolejność inicjalizacji jest brana z kolejności pól w definicji klasy, a nie układu w pamięci, jaki wyszedł kompilatorowi. |
Możliwe, że pomieszałem te dwa pojęcia. Ale pytanie wciąż pozostaje otwarte ze względu na nowsze wersje biblioteki, gdzie foobar{foo,bar} w wersji 2.0 może oznaczać to samo, co foobar{bar,foo} w wersji 1.0. Chodzi o to, aby móc wychwytywać takie zmiany lub mieć kod działający przy dowolnej kolejności pól. |
|
pekfos |
» 2019-01-30 16:13:03 To tak jakby była funkcja przyjmująca 2 liczby int i w nowszej wersji biblioteki bez powodu argumenty były zamienione. Jeśli biblioteka wprowadza breaking changes, to powinieneś o tym przeczytać w changelogu, a nie polegać na kompilatorze. A jeśli biblioteka zrzuca kompatybilność wsteczną bez powodu, to zapewne powinieneś zmienić bibliotekę. |
|
garlonicon Temat założony przez niniejszego użytkownika |
» 2019-01-30 19:37:50 Aha, już pamiętam, dlaczego tak uparcie chciałem użyć listy inicjalizacyjnej konstruktora. Chodziło o taki przypadek: class Wrapper { const Foobar foobar; const int number; public: Wrapper( int foo, int bar, int number ); }; To tak jakby była funkcja przyjmująca 2 liczby int i w nowszej wersji biblioteki bez powodu argumenty były zamienione. |
A czy istnieje jakiś sposób, aby wywołać funkcję odwołując się do nazw argumentów? Raczej nie za bardzo, bo te nazwy mogą być różne w deklaracji i definicji, a samych deklaracji może być wiele i w każdej może wystąpić inna nazwa. Poza tym, mogą również istnieć nienazwane argumenty, wystarczy wtedy podać sam typ. W przypadku struktury tak nie jest, więc to porównanie wydaje się trochę nietrafione. Jeśli biblioteka wprowadza breaking changes, to powinieneś o tym przeczytać w changelogu, a nie polegać na kompilatorze. A jeśli biblioteka zrzuca kompatybilność wsteczną bez powodu, to zapewne powinieneś zmienić bibliotekę. |
Czyli rozumiem, że mogę śmiało wszędzie po kolei wypełniać pola przez foobar{foo,bar} i traktować każdą zmianę kolejności pól jako naruszenie zgodności wstecznej? |
|
« 1 » 2 |