2017-07-14 54 views
5

Właśnie discoverd następujące zachowanie: posiadanie obiektu typu B pochodzącej od rodzaju A ostateczny typ trakcie budowy jest AA i nie B. Można to zaobserwować w poniższym przykładzie:Rodzaj obiektu zmieniającego się w czasie budowy

#include <iostream> 
#include <typeinfo> 

class A 
{ 
    public: 
     A() { std::cout << &typeid(*this) << std::endl; } 
}; 

class B : public A 
{ 
    public: 
     B() : A() { std::cout << &typeid(*this) << std::endl; } 
}; 

int main() 
{ 
    A a; 
    B b; 
    return 0; 
} 

przebiegu tego kodu (skompilowane z gcc 4.8.5) jest następujący:

0x400ae0 
0x400ae0 
0x400ac0 

Widzimy, że typ zwracany przez typeid w A::A() to A, a nie B, a następnie ostateczny typ zmienia się na B.

Dlaczego?

Czy możliwe jest poznanie "prawdziwego" typu końcowego podczas budowy klasy macierzystej?

Moja kontekst jest następujący:

Mam klasa dominująca Resource i kilka klas dziedziczeniu od niego. Mam również ResourceManager powiadomiony o każdym utworzeniu zasobu i konieczności zapoznania się z ostatecznym typem utworzonego zasobu. Co robię, aby uniknąć duplikatów kod jest następujący, ale to nie działa:

class Resource 
{ 
    public: 
    Resource() { ResourceManager::notifyCreation(*this); } 
    ~Resource() { ResourceManager::notifyDestruction(*this); } 
}; 
class MyResource : public Resource 
{ 
    // I don't have to care to the manager here 
}; 

wiem, że mogę zrobić zawiadomienie w każdym konstruktora/destruktora dzieci, ale jest to mniej wytrzymałe (możliwy błąd jeśli zasób zostanie wprowadzony bez powiadomienia do menedżera). Masz pomysł na obejście tego problemu?

+2

Umm ... 'i typeid (....)' ?? – WhiZTiM

+3

'' this' w 'A' jest zawsze' A', dlaczego oczekiwałeś, że 'typeid' będzie inny? –

+0

@WhiZTiM dlaczego? Zwracana wartość typeid jest unikalna w pamięci dla każdego typu. – Caduchon

Odpowiedz

5

Wygląda na to, czego szukasz CRTP

template<typename Concrete> 
struct Resource 
{ 
    Resource() { ResourceManager::notifyCreation(*static_cast<Concrete*>(this)); } 
    ~Resource() { ResourceManager::notifyDestruction(*static_cast<Concrete*>(this)); } 
}; 

struct MyResource : Resource<MyResource> 
{ 

}; 

Zauważ, że MyResource nie jest jeszcze zakończona, gdy zbudowane jest wywołanie notifyCreation. Adres instancji MyResource można pobrać, ale to wszystko, co można zrobić dla instancji. (Dzięki Caleth za wskazanie na to uwagę)

W szczególności [class.cdtor]

Jeśli operand typeid odnosi się do obiektu w trakcie budowy lub zniszczenia i statycznego typu argumentu nie jest ani konstruktor lub klasa destructor za ani jedna z jego podstaw, zachowanie jest niezdefiniowane.

Dlatego ResourceManager musiałyby być realizowane nieco tak, aby umożliwić użyciu typeid

struct ResourceManager 
{ 
    template<typename T> 
    void notifyCreation(T&&) 
    { 
     add(typeid(T)); // can't apply to an expression 
    } 
    template<typename T> 
    void notifyDestruction(T&&) 
    { 
     remove(typeid(T)); // can't apply to an expression 
    } 
}; 
+0

Jest to bezpieczne tylko wtedy, gdy 'MyResource' nie ma członków innych niż" Resource " – Caleth

+1

@Caleth Nie masz na myśli więcej klas bazowych? Tak, ale pokazany kod OP wygląda tak i nie spekulowałem, co jeszcze chce OP. –

+0

Nie Chodzi mi o to, że nie ma więcej członków danych. Nie zostały zainicjowane w miejscu, w którym wywołano 'ResourceManager :: notifyCreation' i zostały zniszczone, gdy' ResourceManager :: notifyDestruction' jest nazywane – Caleth

1

Nie ma dobrego sposobu, aby to zrobić w konstruktorze jak w przykładzie, ale można zapewnić specjalną konstruktora dla A, tj

A(const std::type_info &info) { 
    std::cout << info.name() << std::endl; 
} 

aw B

B() : A(typeid(*this)) { 
    std::cout << typeid(*this).name() std::endl; 
} 

jeśli robisz to na zewnątrz konstruktora, można również funkcję wirtualnego w „A” i zastąpienie go „B”.