Zasadność rzutowania

0

No dobra, kotlet zrobił się gorący i może uda się tutaj coś uzyskać

czym jest rzutowanie ? A w zasadzie jak rozumieć EFEKT rzutowania dwóch różnych od siebie typów ? Bo w Qt bardzo często ta technika jest wykorzystywana.

Kodu nie podam, bo pod ręką nie mam ale mniej więcej do rzeczy. Mam kila typów int, double i char - wiadomo, że jeden typ na drugi mogę rzutować i wiem czego po rzutowaniu się spodziewać - we wszystkich przypadkach będzie to liczba, która coś mi powie i ten wynik w jakiś sensowny sposób zinterpretuję np

int main()
{
    char a='a';
    int liczba1=64;
    double liczba2=22.3;

    cout << static_cast<int>(a) << endl;
    cout << static_cast<char>(liczba1) << endl;
    cout << static_cast<char>(liczba2) << endl;
}

powyższe rzutowanie jest dość intuicyjne.

Natomiast w programowaniu obiektowym ściślej mówiąc -dziedziczenie-, spotkałem się z opinią, że rzutowanie można wykorzystać do tego, aby dostać się do metod klasy nadrzędnej tj, rzutowanie w dół - w sensie, klasy OD której jest dziedziczenie - I pytanie jest takie - czy jest to prawdą ?

drugie pytanie jest takie - jak rozumieć WYNIK rzutowania ? UWAGA - podaję od czapy dwa zupełnie różne i nie związane ze sobą obiekty typu "button" i "window" - żebyśmy mieli jasność, wiem, że wynik będzie z d#%$py - ale może ktoś was poda sensowny przykład na obiektach, które po sobie dziedziczą i są ze sobą w jakiś sposób związane i jak rozumieć wynik takiego rzutowania

na te myśli nie znalazłem do dzisiaj satysfakcjonującej odpowiedzi - może tutaj się uda ?

0

Na moje oko nie powinno się rzutować typów, które nie są powiązane ze sobą na równi - czyt. przechowują całkowicie inne dane. Przykład z buttonem i window może być też trochę mylący, bo w zasadzie button (przynajmniej w winapi) to też okno, czyli window. Ja na przykład rzutuję tylko podczas operacji na pointerach, które mogą mi podać jakiś result, którego nie miałbym, gdybym korzystał z "macierzystej" klasy danego obiektu reprezentowanego przez pointer (w oprogramowaniu, które teraz piszę, jest dużo takich operacji - na przykład dostaję info o wysokości, na której znajduje się obiekt, pobierając tylko wskaźnik na określonym adresie i ten wskaźnik rzutuję na strukturę reprezentującą to, co wiem, że tam jest)

5

Rzutowanie to nic innego jak zignorowanie systemu typów języka.

0
Riddle napisał(a):

Rzutowanie to nic innego jak zignorowanie systemu typów języka.

szczerze mówiąc mnie nic nie mówi zignorowanie systemu typów - chyba, że dobrze rozumiem, że chodzi o taki oto obrazek

int a;
char b;

to kompilator odczyta to jako

a;
b;

tylko nadal nie łapię jaki jest sens tego - pytań nie powielam i są aktualne jak w pierwszym wątku

1

czym jest rzutowanie ?

Traktowaniem zmiennej jednego typu jako zmiennej innego typu.

A w zasadzie jak rozumieć EFEKT rzutowania dwóch różnych od siebie typów ?

Bardzo, bardzo różnie.

Są w C++ cztery podstawowe metody rzutowania:

  1. dynamic_cast, rzutujący dynamicznie (podczas działania programu) obiekt jednego typu na drugi, jeśli oba te typy są powiązane ze sobą dziedziczeniem — może rzutować ojca na syna, syna na ojca, brata na brata, itd.
  2. static_cast, rzutujący statycznie (podczas kompilacji) obiekt jednego typu na drugi, jeśli istnieją wbudowane lub zdefiniowane przez programistę regułki rzutowania. Jest to chyba najczęściej używany sposób rzutowania.
  3. const_cast, pozwalający w pewnych warunkach porzucenie consta lub volatile’a. Używany chyba najrzadziej, głównie do irytowania współpracowników.
  4. reinterpret_cast, czyli „zapomnij na chwilę, że to nie ma sensu i zrób, co ci każę”. Dalej ma swoje wymagania (np. ten sam rozmiar), ale może robić bardzo wiele rzeczy, z których olbrzymia większość jest bez sensu.

Natomiast w programowaniu obiektowym ściślej mówiąc -dziedziczenie-, spotkałem się z opinią, że rzutowanie można wykorzystać do tego, aby dostać się do metod klasy nadrzędnej tj, rzutowanie w dół - w sensie, klasy OD której jest dziedziczenie - I pytanie jest takie - czy jest to prawdą ?

Tak, jest to prawdą. Służy do tego dynamic_cast.

drugie pytanie jest takie - jak rozumieć WYNIK rzutowania ? UWAGA - podaję od czapy dwa zupełnie różne i nie związane ze sobą obiekty typu "button" i "window" - żebyśmy mieli jasność, wiem, że wynik będzie z d#%$py - ale może ktoś was poda sensowny przykład na obiektach, które po sobie dziedziczą i są ze sobą w jakiś sposób związane i jak rozumieć wynik takiego rzutowania

Wynikiem rzutowania obiektu x klasy A na obiekt klasy B jest obiekt klasy B, który, na tyle ile się dało, ma możliwie dużo wspólnego z x. Jest tu bardzo wiele pułapek — w szczególności, wiele rzutowań jest implementation defined, albo i nawet undefined — ale taka jest ogólna idea.

Jak otworzysz sobie książkę, którą masz, na indeksie, i poszukasz ww. poleceń, albo samego cast, albo rzutowanie, to znajdziesz więcej na ten temat, z przykładami i w ogóle…

3
zkubinski napisał(a):

tylko nadal nie łapię jaki jest sens tego - pytań nie powielam i są aktualne jak w pierwszym wątku

@zkubinski jeśli nie spotkałeś się jeszcze z potrzebą rzutowania, to nie stosuj go - nie jest Ci potrzebne.

Ale Tworząc duże aplikacje, być może korzystające z różnych bibliotek, możesz natrafić na sytuację w której system typów C++ ustali jeden typ zmiennej, a Ty chcesz jej użyć jako inny typ. Innymi słowy, C++ zna typ zmiennej na poziomie kompilacji. Jeśli w runtime chcesz spróbować użyć tej wartości jako inny typ niż zadeklarowany, to normalnie język nie pozwoli Ci tego zrobić - chyba że rzutujesz typy.

4

Z jakiej książki się uczysz? Nie wierzę, że nie zostało w niej opisane rzutowanie.

0

Co masz na myśli przez "wynik rzutowania", i czym to się różni od wyjaśnienia "czym jest rzutowanie"?

0

jeden z przykładów - podaję zrzut ekranu aby zobrazować o co chodzi mi w pytaniu pierwszym czyli - czy za pomocą rzutowania mogę dostać się do składowych innej klasy

screenshot-20221221143407.png

drugie pytanie brzmi - jak rozumieć wynik rzutowania ?
i dołożę jeszcze trzecie pytanie - czy obiekt Z którego chcę wykonać rzutowanie "wzbogaca" się o składowe obiektu NA który rzutuję ?

1

Uprzedzam, że wiem czym jest rzutowanie i umiem się nim obsłużyć

Minutę później wklejasz screena, który wyraźnie pokazuje, że tak nie jest. Serio, idź do książki zamiast marnować nam wszystkim czas. Sobie też, ale i tak go widać nie cenisz.

3

Dostałeś już odpowiedzi na wszystkie te pytania. Obiekt po rzutowaniu jest obiektem nowego typu — tego, na który go zrzutowałeś.

  1. Możesz próbować użyć wszystkich składowych innej klasy — co najwyżej dostaniesz segfaultem na twarz, jak, powiedzmy, coś wirtualnego nie będzie działać.
  2. Wynikiem rzutowania jest obiekt nowego typu. Tak jak Ci pisałem wyżej.
  3. Rzutowanie robi nowy obiekt. Stary obiekt dalej istnieje na takich samych zasadach, jak wcześniej. Jak rzutujesz jakieś wskaźniki czy robisz inne cuda, to może Ci wybuchnąć na milion sposobów, ale to już inna kwestia.
1
class base {
public:
    std::string class_name() {
        return "base";
    }
};
class derived : public base {
public:
    std::string class_name() {
        return "derived";
    }
};

int main() {
    derived d;
    std::cout << d.class_name() << " " << static_cast<base>(d).class_name() << "\n";
}

Przemyśl ten przykład.

0

@Althorion:

rzutowanie jest po to, żeby udawać, że zmienna jest innego typu, niż jest. Bo, na przykład, klasa-syn przykrywa metodę klasy-ojca, a ty bardzo chcesz użyć metody klasy-ojca. To tego się tyczy to, o czym enedil pisał — tzn. dziedziczenie w kontekście rzutowania — bo wtedy, i praktycznie wyłącznie wtedy, ma ono sens.

ok, brzmi już rozumnie i zaczynam łapać. Zrobiłem kod

wykonuję rzutowanie w górę -CELOWO- ale obiekt obj1 nie ma metody z obiektu 2... - coś poyebayem ?

@kq: POPRAWIONE - o ile dobrze

class Obiekt1
{
public:
    void funkcja(){
        cout << "obiekt 1" << endl;
    }
};

class Obiekt2 : public Obiekt1
{
public:
    void funkcja(){
        cout << "obiekt 2" << endl;
    }
};

int main()
{
    Obiekt1 obj1;
    Obiekt2 obj2;

    static_cast<Obiekt1>(obj2).funkcja();
}
0

Tak. Robisz cast na typ Obiekt1 — typ, na który castujesz, ląduje w nawiasach ostrych <>. Więc zrobiłeś sobie w obj1 obiekt typu Objekt1, który przypomina obj2. Obiekty typu Obiekt1 mają funkcja() wypisującą obiekt 1. Więc i obj1 to robi.

Na przyszłość, uwzględnij też fakt, że jak masz raz zadeklarowany typ zmiennej, to ona już od tego momentu będzie takiego typu. Więc nawet, jak będziesz coś-tam castował, to kompilator Ci pewnie prze-castuje jeszcze raz, jeśli będzie potrzebował, by się typy zgadzały.

EDYCJA:
Wersja po poprawce powinna już robić dokładnie to, czego się spodziewałeś.

EDYCJA 2:
Moje angielskie wydanie „C++ Programming Language”, czwarta edycja, ma omówione rzutowanie na 298. stronie i dalej.

1

Ok, ale przestań spamować tematami na które odpowiedź możesz znaleźć w internecie i przestań nie odwoływać sie do rad które są Ci udzielane. Inaczej ludzie będą Ci pisać nieladnie, bo plujesz na ich poświęcony Tobie czas. Ktoś pomaga a Ty i tak swoje. Programowanie to głównie nauka na własnych błędach i własne badanie/cwiczenia. Ty jesteś rozpieszczonym bachorem i chcesz mieć wszystko na tacy podane myśląc, że siedzą tu sami twoi prywatni korepetytorzy. Najgorsze jest to, że jesteś stary. Stary a dziecko.
Jeżeli naprawdę chcesz się nauczyć a nie irytować innych, albo lechtac sobie ego to zacznij w końcu mozolnie pracować. Robić ćwiczenia notatki itd.
Innymi słowy, zacznij się w końcu uczyć.

0
enedil napisał(a):
class base {
public:
    std::string class_name() {
        return "base";
    }
};
class derived : public base {
public:
    std::string class_name() {
        return "derived";
    }
};

int main() {
    derived d;
    std::cout << d.class_name() << " " << static_cast<base>(d).class_name() << "\n";  // #1
}

Linijkę #1 można zastąpić tym:

std::cout << d.class_name() << ' ' << d.base::class_name() << '\n';

Generalnie przysłanianie niewirtualnych metod uważam za szkodliwe, więcej z tego problemów niż pożytku.
Przy virtual kompilator może pomóc (dzięki override/final/=0) a przysłanianie... szukaj wiatru w polu ;)

1
enedil napisał(a):
class base {
public:
    std::string class_name() {
        return "base";
    }
};
class derived : public base {
public:
    std::string class_name() {
        return "derived";
    }
};

int main() {
    derived d;
    std::cout << d.class_name() << " " << static_cast<base>(d).class_name() << "\n";
}

Przemyśl ten przykład.

Ogółem uważam, że takie przykłady robią więcej szkody niż pożytku. Ponawiając wcześniejszy cytat z Einsteina: "Make everything as simple as possible, but not simpler". Takiego kodu nie przepuściłbym przez code review, więc nie powinien on być pokazywany nowicjuszom (a na takim poziomie jest zkubiński).

  1. to jest efektywnie std::cout << d.class_name() << " " << base{d}.class_name() << "\n"; - czyli utworzenie nowego obiektu. O to praktycznie nigdy nie chodzi w rzutowaniu. Inaczej mówiąc, nie ma zasadniczej różnicy między
derived d;
base b = d;
std::cout << d.class_name() << " " << b.class_name() << "\n";

i

derived d;
base b = static_cast<b>(d);
std::cout << d.class_name() << " " << b.class_name() << "\n";

Za to, co ważniejsze, adresy obiektów d i b (lub utworzonego przez cast) się różnią.

  1. Jest to szczególnie istotne gdy weźmiemy pod uwagę polimorfizm dynamiczny
class base {
public:
    virtual std::string class_name() {
        return "base";
    }
};
class derived : public base {
public:
    std::string class_name() override {
        return "derived";
    }
};

int main() {
    derived d;
    std::cout << d.class_name() << " " << static_cast<base>(d).class_name() << "\n";
    // oops!
}
  1. W prawdziwym kodzie właściwie zawsze (pomijając typy wbudowane) operujemy na referencjach i wskaźnikach, i tam też jest to najbardziej istotne.
  2. Wcale nie jest to nawet prostsze w tłumaczeniu, wystarczy wyjść ciut dalej z przykładami i mamy np.
struct base
{
    std::string name = "base";
};
struct derived: base
{
    std::string name = "derived";
};

int main() {
    derived d1, d2;
    static_cast<base>(d1).name = "lol";
    static_cast<base&>(d2).name = "lol";
    DBG(d1.base::name)
    DBG(d1.derived::name)
    DBG(d2.base::name)
    DBG(d2.derived::name)
}

Tłumaczenie dlaczego wynik jest taki, jak poniżej jest wg mnie bardziej kłopotliwe niż nauczenie poprawnie od razu.

d1.base::name                                               base
d1.derived::name                                            derived
d2.base::name                                               lol
d2.derived::name                                            derived

Szczególnie, jeśli wcześniej mówiliśmy że castujemy obiekt i na nim pracujemy, a okazuje się, że po prostu utworzyliśmy sobie nowy poprzez slicing.

2

Nie trzeba do tych wszystkich rzeczy żadnego rzutowania!!!
https://wandbox.org/permlink/hUViWdnS6K60bJmz

#include <iostream>
using namespace std;

class base
{
    public:
    void foo() { cout<<"World"<<endl; }
};

class derived:public base
{
    public:
    void foo() { cout<<"Hello"<<endl; }
};

int main()
{
    derived d;
    d.foo();
    d.base::foo();
    return 0;
}
2
Riddle napisał(a):

Rzutowanie to nic innego jak zignorowanie systemu typów języka.

Mam problem z tym stwierdzeniem.

Powiedz mi, czym się rożni taki kod:

struct Typ1 {
    explicit operator Typ2() const {
        return Typ2(blablabla);
    }
}

Od takiego kodu:

struct Typ2 {
    Typ2(Typ1 typ1) {
        blablabla;
    }
}

Trudno powiedzieć, by ten drugi snippet w jakikolwiek sposób "ignorował system typów". Przeciwnie, w pełni szanując system typów tworzy nowy obiekt typu Typ2 na podstawie istniejącego obiektu typu Typ1. W żadnym punkcie system typów nie został naruszony nawet odrobinkę.

Z kolei ten pierwszy snippet definiuje rzutowanie, a zatem, idąc twoim tokiem rozumowania, pozwala na zignorowanie systemu typów. Tyle że... ten pierwszy snippet robi dokładnie to samo, co ten drugi snippet! Tworzy nowy obiekt typu Typ2 na podstawie istniejącego obiektu typu Typ1.

System typów rzeczywiście pozwala zignorować reinterpret_cast, ale przecież reinterpret_cast dalece nie wyczerpuje rzutowania, przeciwnie, powinno być używane jak najrzadziej i z najwyższą ostrożnością, bo łatwo o UB. Lwia część rzutowania to powinien static_cast, domyślnie rzutowanie powinno oznaczać static_cast właśnie, a static_cast nie może być IMO uważany za żaden wytrych w systemie typów.

2

Pokazałeś przykład konwersji. A to co innego niż rzutowanie, pomimo tego, że jedno i drugie można uzyskać za pomocą static_cast.

0
YetAnohterone napisał(a):
Riddle napisał(a):

Rzutowanie to nic innego jak zignorowanie systemu typów języka.

Mam problem z tym stwierdzeniem.

Powiedz mi, czym się rożni taki kod:

struct Typ1 {
    explicit operator Typ2() const {
        return Typ2(blablabla);
    }
}

Od takiego kodu:

struct Typ2 {
    Typ2(Typ1 typ1) {
        blablabla;
    }
}

Różni się tym samym co taboret od ciebie, czyli nie ma nic wspólnego oprócz tego że czasami używasz taboretu co trudno nazwać czymś wspólnym.
W Typ2 deklarujesz konstruktor - (przedstawiasz konstrukcje taboretu).
W Typ1 używasz konstruktora Typ2 (używasz taboret).

0

rzutowanie ze wskaźnikami

class Base
{
public:
    void function(){
        std::cout << "function Base" << std::endl;
    }
};

class Deriverd : public Base
{
public:
    void function(){
        std::cout << "function Derived" << std::endl;
    }
};

int main()
{
    Deriverd *dr;
    dr = new Deriverd;

    static_cast<Base*>(dr)->function();

    return 0;
}

czy o to wam chodziło, że przykłady mogą narobić więcej szkody niż pożytku ?

0

Czemu dodałeś inline? — enedil 13 minut temu

@zkubinski: Zanim skutecznie usłyszysz odp na jedno pytanie walisz następne, jak nadpobudliwy czterolatek.

A co do słowa kluczowego inline traktuj jakby w 2022 nie istniało

0
_13th_Dragon napisał(a):

Różni się tym samym co taboret od ciebie, czyli nie ma nic wspólnego oprócz tego że czasami używasz taboretu co trudno nazwać czymś wspólnym.
W Typ2 deklarujesz konstruktor - (przedstawiasz konstrukcje taboretu).
W Typ1 używasz konstruktora Typ2 (używasz taboret).

Uważam, że obaj mamy rację, tylko patrzymy na sprawę z innej strony.

Z punktu widzenia składni języka: oczywiście ty masz rację.

Ale w żaden sposób nie wpływa to na zasadność mojego wniosku, że

YetAnohterone napisał(a):

Tyle że... ten pierwszy snippet robi dokładnie to samo, co ten drugi snippet! Tworzy nowy obiekt typu Typ2 na podstawie istniejącego obiektu typu Typ1.

Oba snippety w ten sam sposób nie ignorują systemu typów, a o to mi chodziło.

tajny_agent napisał(a):

Pokazałeś przykład konwersji. A to co innego niż rzutowanie, pomimo tego, że jedno i drugie można uzyskać za pomocą static_cast.

Tu z kolei przyznaję rację, palnąłem bzdurę. W moim rozumieniu konwersja była szczególnym przypadkiem rzutowania. Sprawdziłem to, myliłem się i przepraszam za napisanie bzdury.

1 użytkowników online, w tym zalogowanych: 0, gości: 1