Upcasting potomka na abstrakciu

Upcasting potomka na abstrakciu
« kdy: 15. 12. 2020, 01:04:48 »
Dobrý deň, ako správne upcastnúť potomka na predka? Skúšal som rôzne spôsoby, ale pri väčšine som dostal chybu: Error: 'IClipboardReader': cannot instantiate abstract class. Prvý (z ľava do prava) a posledný spôsob (pointer) mi funguje. Viete mi prosím vysvetliť prečo ostatné nie? A ako sa to, upcastuje správne (ale z prava do ľava)?

Kód: [Vybrat]
using binaries = std::vector<char>;

class IClipboardReader
{
public:
virtual binaries Read() const = 0;
};

class IClipboardWriter
{
public:
virtual void Write(binaries bins) = 0;
};

class ClipboardSlot : public IClipboardReader, IClipboardWriter
{
public:
binaries Read() const override
{
// ...
return { 'a', 'b', 'c' };
}

void Write(binaries data) override
{
// ...
}
};

template<typename CharType = char>
std::basic_ostream<CharType>& operator<<( // vypisanie vectoru
std::basic_ostream<CharType>& out,
const std::vector<CharType>& value
)
{
for (auto& c : value) out << c; return out;
}

void main()
{
auto slot = ClipboardSlot();
IClipboardReader& upcasted1 = slot; // OK
auto upcasted2 = (IClipboardReader&)slot; // Error: 'IClipboardReader': cannot instantiate abstract class
auto upcasted3 = std::forward<const IClipboardReader&>(slot); // Error: 'IClipboardReader': cannot instantiate abstract class
auto upcasted4 = static_cast<const IClipboardReader&>(slot); // Error: 'IClipboardReader': cannot instantiate abstract class

auto slotPtr = std::make_shared<ClipboardSlot>();
auto upcasted5 = static_cast<std::shared_ptr<IClipboardReader>>(slotPtr); // OK

std::cout << "upcasted1:" << upcasted1.Read() << std::endl;
std::cout << "upcasted2:" << upcasted2.Read() << std::endl;
std::cout << "upcasted3:" << upcasted3.Read() << std::endl;
std::cout << "upcasted4:" << upcasted4.Read() << std::endl;
std::cout << "upcasted5:" << upcasted5->Read() << std::endl;
}
« Poslední změna: 15. 12. 2020, 01:08:17 od fortran1986 »


Re:Upcasting potomka na abstrakciu
« Odpověď #1 kdy: 15. 12. 2020, 01:10:08 »
Pre jednoduchšie čítanie kódu som to hodil aj na https://pastebin.com/kiwWjcd5

Re:Upcasting potomka na abstrakciu
« Odpověď #2 kdy: 15. 12. 2020, 08:30:55 »
Já bych zkusil normální IClipboardReader reader = slot; Ale nenapsal jste ani co je to za jazyk. Vypadá to sice jako C++, ale co když je to něco jiného…

Re:Upcasting potomka na abstrakciu
« Odpověď #3 kdy: 15. 12. 2020, 09:26:54 »
Samotné auto (u upcasted2-4) nemůže být reference. Musíš použít auto&.

Re:Upcasting potomka na abstrakciu
« Odpověď #4 kdy: 15. 12. 2020, 10:05:36 »
Já bych zkusil normální IClipboardReader reader = slot; Ale nenapsal jste ani co je to za jazyk. Vypadá to sice jako C++, ale co když je to něco jiného…
Je to C++, takže IClipboardReader reader = slot; nebude fungovat.
IClipboardReader je abstraktní třída (interface). Nedají se od ní vytvářet instance. Jdou jenom pointery a reference. A samotná instance musí být něco odvozeného.

Fungovat bude třeba :
Kód: [Vybrat]
IClipboardReader &reader = slot;
IClipboardReader *reader = &slot;

Kód: [Vybrat]
IClipboardReader reader = slot;
dělá slicing. Zkopíruje bázovou třídu do nové instance. A protože je abstraktní, tak to nejde. To je ta hláška "cannot instantiate abstract class"

Pro Fortrana :
Kód: [Vybrat]
auto upcasted2 = (IClipboardReader&)slot; // Error: 'IClipboardReader': cannot instantiate abstract class
auto upcasted3 = std::forward<const IClipboardReader&>(slot); // Error: 'IClipboardReader': cannot instantiate abstract class
auto upcasted4 = static_cast<const IClipboardReader&>(slot); // Error: 'IClipboardReader': cannot instantiate abstract class
dělá taky slicing. Auto dělá z referencí hodnoty, pokud se mu neřekne jinak. Dokonce bych řekl, že je to rozumné defaultní chování. C++ není Java. Chování podobné intům je žádoucí.

Druhá věc je, proč ten cast vůbec chcete ručně dělat. Volat metody předka jde i na potomkovi. A pokud budete volat nějakou funkci co bere referenci na předka, tak ten cast udělá překladač sám. Za sebe si nepamatuju, kdy jsem potřeboval ručně castit na předka. Je to fakt vzácné.

Jinak std::forward slouží k forwardování obecných parametrů a k ničemu jinému. Uvnitř je to sice cast, ale nepoužívejte to tak. Pokud nepíšete nějakou optimalizovanou ale zároveň generickou šablonu, tak forward nechcete používat. V běžném kódu se vyskytuje minimálně.


Re:Upcasting potomka na abstrakciu
« Odpověď #5 kdy: 17. 12. 2020, 23:28:13 »
Samotné auto (u upcasted2-4) nemůže být reference. Musíš použít auto&.

Ďakujem vyskúšam.

Já bych zkusil normální IClipboardReader reader = slot; Ale nenapsal jste ani co je to za jazyk. Vypadá to sice jako C++, ale co když je to něco jiného…

Dobrý deň, toto som skúšal ako prvé, vo vyšších jazykoch to takto pekne jednoducho funguje. Ale v C++ neni vyšší jazyk a všetko mu treba pekne po lopate vysvetliť. Čo je na druhej strane aj výhoda lebo aspoň programátor pochopí ako to funguje.

Já bych zkusil normální IClipboardReader reader = slot; Ale nenapsal jste ani co je to za jazyk. Vypadá to sice jako C++, ale co když je to něco jiného…

Je to C++, takže IClipboardReader reader = slot; nebude fungovat.
IClipboardReader je abstraktní třída (interface). Nedají se od ní vytvářet instance. Jdou jenom pointery a reference. A samotná instance musí být něco odvozeného.

Fungovat bude třeba :
Kód: [Vybrat]
IClipboardReader &reader = slot;
IClipboardReader *reader = &slot;

Kód: [Vybrat]

IClipboardReader reader = slot;
dělá slicing. Zkopíruje bázovou třídu do nové instance. A protože je abstraktní, tak to nejde. To je ta hláška "cannot instantiate abstract class"

Pro Fortrana :
Kód: [Vybrat]
auto upcasted2 = (IClipboardReader&)slot; // Error: 'IClipboardReader': cannot instantiate abstract class
auto upcasted3 = std::forward<const IClipboardReader&>(slot); // Error: 'IClipboardReader': cannot instantiate abstract class
auto upcasted4 = static_cast<const IClipboardReader&>(slot); // Error: 'IClipboardReader': cannot instantiate abstract class
dělá taky slicing. Auto dělá z referencí hodnoty, pokud se mu neřekne jinak. Dokonce bych řekl, že je to rozumné defaultní chování. C++ není Java. Chování podobné intům je žádoucí.

Druhá věc je, proč ten cast vůbec chcete ručně dělat. Volat metody předka jde i na potomkovi. A pokud budete volat nějakou funkci co bere referenci na předka, tak ten cast udělá překladač sám. Za sebe si nepamatuju, kdy jsem potřeboval ručně castit na předka. Je to fakt vzácné.

Jinak std::forward slouží k forwardování obecných parametrů a k ničemu jinému. Uvnitř je to sice cast, ale nepoužívejte to tak. Pokud nepíšete nějakou optimalizovanou ale zároveň generickou šablonu, tak forward nechcete používat. V běžném kódu se vyskytuje minimálně.


Ďakujem Vám za podrobné vysvetlenie.

Prečo to chcem pretypovať? Občas chcem pred programátorom zobraziť len metódy predka. Aby som ho nerozptyloval metódami potomka. Dajme tomu že si urobíte Modul File a v ňom funkciu File::GetStat ktorá vám vracia kompletný objekt FileStat obsahujúci info o súbore (časy modifikácie, IdSkupiny, IdJednotky, Velkosť súboru). Ale kôli užívateľskej prívetivosti si možno chcete urobiť aj wrapper File::GetTimes, od ktorého, ale programátor očakáva že bude obsahovať len časy ale už nie IdSkupiny IdJednotky a podobne. Viem že čistejší sposob by bolo ručné namapovanie objektu FileStat na FileTimes lenže takto je to jednoduchšie a hlavne omnoho rýchlejšie.