C++ downcast this v předkovi na potomka

C++ downcast this v předkovi na potomka
« kdy: 14. 02. 2021, 15:32:43 »
Ahojte akým spôsobom získam inštanciu potomka v predkovi? Reálny príklad by mal logovať exceptiony ale to sem nebudem dávať. Urobil som zjednodušený príklad toho čo potrebujem:

Kód: [Vybrat]
class Base
{
public:
Base()
{
std::cout << "type: " << typeName(); // tu chcem mat logovanie typu
}

virtual std::string typeName() const
{
return typeid(*this).name();
}
};

class Child : public Base
{
public:
using Base::Base;
};

auto child1 = Child(); // vypise sa "type: class Base"
auto child2 = std::make_shared<Child>(); // vypise sa "type: "type: class Base"

a ja potrebujem vypisat pre typ Child "type: class Child". Alebo teda kludne moze byt názov typu aj s pointerom.  Chápem že this v predkovi je upacstnué na predka. Dalo by sa to downcastnúť na Child... ale tam už potrebujem názov typu na, ktorý to downcastnem:

Kód: [Vybrat]
template<typename ChildType>
class Base
{
public:
Base()
{
std::cout << "type: " << typeName(); // tu chcem mat logovanie typu
}
virtual ~Base() = default;

virtual std::string typeName() const
{
const auto derived = dynamic_cast<ChildType *>(const_cast<Base *>(this));
return typeid(derived).name();
}
};

class Child : public Base<Child>
{
public:
using Base::Base;
};
auto child1 = Child(); // vypise sa "type: class Child * __ptr64"
auto child2 = std::make_shared<Child>(); // vypise sa "type: class Child * __ptr64"

Nedalo by sa to nejako downcastnúť aj bez toho aby som musel názov typu predať ako template parameter? Nejako použiť type inference alebo tak?
« Poslední změna: 14. 02. 2021, 16:59:49 od Petr Krčmář »


Idris

  • *****
  • 1 247
    • Zobrazit profil
    • E-mail
Re:C++ downcast this v predkovi na potomka
« Odpověď #1 kdy: 14. 02. 2021, 16:14:14 »
typeid(*this).name() vrátí jméno typu instance, na typu this nezáleží. Metoda typeName ani nemusí být virtuální (pokud tam je třeba ten virtuální destruktor nebo jiná metoda, u které je důvod, aby byla virtuální).

Pokud nevadí manglování jmen (MSVC a ICC vypisují civilizovaně, třeba clang ale moc ne), tak tohle stačí.

(Proměnná s instancí musí ale pochopitelně být ukazatel.)
« Poslední změna: 14. 02. 2021, 16:21:19 od Idris »

Re:C++ downcast this v předkovi na potomka
« Odpověď #2 kdy: 14. 02. 2021, 20:01:43 »
V konstruktoru předka nelze objekt "downcastnout" na potomka, objekt se chová jako instance předka.

Z working draft (N4861, https://timsong-cpp.github.io/cppwp/n4861/class.cdtor):
Citace
The typeid operator ([expr.typeid]) can be used during construction or destruction ([class.base.init]).
When typeid is used in a constructor (including the mem-initializer or default member initializer ([class.mem]) for a non-static data member) or in a destructor, or used in a function called (directly or indirectly) from a constructor or destructor, if the operand of typeid refers to the object under construction or destruction, typeid yields the std​::​type_­info object representing the constructor or destructor's class.
If the operand of typeid refers to the object under construction or destruction and the static type of the operand is neither the constructor or destructor's class nor one of its bases, the behavior is undefined.

Ze stejného důvodu v té druhé ukázce ten dynamic_cast "selže" a hodnota proměnné derived je nullptr.

Co třeba pro vytváření objektu místo přímo make_shared použít nějakou vlastní podobnou funkci, která nejdříve objekt vytvoří, a pak buď přímo provede ten výpis nebo teda zavolá tu virtuální metodu na tom hotovém objektu.

nula

Re:C++ downcast this v předkovi na potomka
« Odpověď #3 kdy: 15. 02. 2021, 07:22:01 »
O typeid uz tady psali jini, k tomu se uz nebudu vracet (tbh, ani jsem to nevedel), ale chtel bych poznamenat neco k volani virtualni metody z konstruktoru:

V C++ nelze volat virtualni metodu z konstruktoru predka. Respektive lze, ale zavola se vzdy metoda z aktualne konstruovane tridy (ve stromu trid). Tedy pokud se zavola v Base::Base() metoda typeInfo, tak prestoze je to virtualni metoda, tak se zavola Base::typeInfo().
Duvod je ten, ze v dobe, kdy se vykonava konstruktor Base tridy, jeste neprobehl konstruktor Child tridy a ani inicializace zadnych atributu Child tridy. Volani virtualni metody by tak bylo potencialne nebezpecne.
Co je na tom blbe, ze prekladace se neobtezuji ani WARNovat - a pritom je to imho dost castym zdrojem chyb (taky se mi to stalo). Blbe je totiz, ze pokud volate sice nevirtualni metodu, ale ta zas vola virtualni, tak to neni ani na prvni pohled videt a v projektu, kde na stejnem kodu dela vic lidi, tak se to lehce prehlidne.
Sice, je to trochu neflexibilni, ale IMHO je to teda jeste porad lepsi, nez v Jave, kde by se v tomto pripade sice "spravne" zavola Child::typeInfo(), nicmene probnlem s inicializaci a poradim konstruktoru je tam uplne stejny, takze vysledek je, ze se vola virtualni metoda z neinicializovane tridy. To byva jeste horsi a jeste hur hledatelny pruser (opet bez nejake zminky prekladace).

Re:C++ downcast this v předkovi na potomka
« Odpověď #4 kdy: 15. 02. 2021, 08:12:00 »
pokud chceš získat z předka ukazatel na potomka musíš to udělat ve dvou krocích, nejprve vše vytvořit a předat ukazatele v hierarchické struktuře a pak teprve použít ukazatel. ještě je tu druhá možnost, že si někam uložíš ID dané třídy a dynamicky ji voláš přes ID uložné v globálním objektu, ale silně bych ti doporučil používat pro něco takového generiky místo teplates !!!


Re:C++ downcast this v předkovi na potomka
« Odpověď #5 kdy: 15. 02. 2021, 16:29:23 »
Virtuální funkci z konstruktoru volat nejde. Potomek v tu dobu ani neexistuje. CRTP zase způsobí, že není jeden předek, ale jen šablona. Proč si teda nepředat potřebné parametry do konstruktoru, který bude protected? Například takto.
Kód: [Vybrat]
#include <string>
#include <typeinfo>
#include <iostream>

class Base {
protected:
    Base(const std::type_info& derivedType) {
        std::cout << "derived: " << derivedType.name() << std::endl;
    }
};

class Derived : public Base {
public:
    Derived() : Base(typeid(Derived)) {}
};

int main() {
    Derived{};
    return 0;
}

Re:C++ downcast this v předkovi na potomka
« Odpověď #6 kdy: 15. 02. 2021, 22:02:10 »
Ďakujem Vám všetkým za odpovede. Aj za tipy a príklady idem ich vyskúšať.

Že derived je nullptr by som si ani nevšimol, keby ste ma na to neupozornili. Mal som si to najprv vydebugovať.  Ale najhoršia je tá logická chyba: snaha volať virtuálne metódy z predka, keď ešte ani ten nebol vykonštruovaný.

Predávať to cez konštruktor ma tiež napadlo, ale popravde hľadal som riešenie ako sa tomu vyhnúť.

Re:C++ downcast this v předkovi na potomka
« Odpověď #7 kdy: 16. 02. 2021, 09:17:18 »
Ještě mě napadlo tam přidat CRTP prostředníka, aby to nemusel člověk všude psát.
Kód: [Vybrat]
#include <string>
#include <typeinfo>
#include <iostream>

class Base {
protected:
    Base(const std::type_info& derivedType) {
        std::cout << "derived: " << derivedType.name() << std::endl;
    }
};

template<typename Derived>
class BaseCrtp : public Base {
protected:
    BaseCrtp() : Base(typeid(Derived)) {}
};

class Derived : public BaseCrtp<Derived> {
public:
    Derived() {}
};

int main() {
    Derived{};
    return 0;
}