Wypełnianie struktury na liście inicjalizacyjnej konstruktora
Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Zarejestruj się!

Wypełnianie struktury na liście inicjalizacyjnej konstruktora

AutorWiadomość
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:
C/C++
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:
C/C++
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:
C/C++
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ć.
P-173799
» 2019-01-30 08:43:44
C/C++
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.
P-173800
» 2019-01-30 10:35:55
C++20 ma tak zwane designated initializers, a wygląda to tak:
C/C++
Wrapper::Wrapper( int foo, int bar, int number )
    : foobar
{.foo = foo,.bar = bar }, //kolejność inicjaliztorów musi być taka sama jak kolejność deklaracji
number( number )
{ }

W GCC 8.1.0 już działa.
P-173801
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:
C/C++
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:
C/C++
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?
P-173804
» 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#19
Poza tym.. Kolejność inicjalizacji jest brana z kolejności pól w definicji klasy, a nie układu w pamięci, jaki wyszedł kompilatorowi.
P-173805
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.
P-173806
» 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ę.
P-173807
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:
C/C++
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?
P-173812
« 1 » 2
 Strona 1 z 2Następna strona