Приведение типа указателя
От: Eldar9x  
Дата: 05.03.08 10:21
Оценка:
Привет всем!
Допустим есть такая иерархия:
class A    {
public:
    virtual void do_it() {           
        std::cout << "a method " << std::endl;
    }
};

class B : public A {
public:
    virtual void do_it() {
        std::cout << "b method " << std::endl;                
    }
};

class C : public B {
public:
    virtual void do_it() {
        std::cout << "c method " << std::endl;                
    }
};


Создаем объект класса и вызываем его метод:
A * a = new C();
a->do_it();

получаем "c method"

получить указатель типа (A*) легко:
A * a = new C();
a->A::do_it(); // "a method"

или
A * a = new C();
a = new A(*A)
a->do_it(); // "a method"


Каким образом привести указатель к типу B*?
int i;
i = (i++)+(i++);
cout << i;
Re: Приведение типа указателя
От: NikeByNike Россия  
Дата: 05.03.08 10:28
Оценка:
Здравствуйте, Eldar9x, Вы писали:

E>Каким образом привести указатель к типу B*?


Если достоверно не известно что за указателем на А скрывается B — нужно использовать dynamic_cast.
Если известно — static_cast, но лично я использую polymorfic_downcast, который в дебаге проверяет что это действительно так.
Нужно разобрать угил.
Re[2]: Приведение типа указателя
От: Eldar9x  
Дата: 05.03.08 10:39
Оценка:
Здравствуйте, NikeByNike, Вы писали:

NBN>Если достоверно не известно что за указателем на А скрывается B — нужно использовать dynamic_cast.

NBN>Если известно — static_cast, но лично я использую polymorfic_downcast, который в дебаге проверяет что это действительно так.

если имелось ввиду
A * a = new C();
dynamic_cast<A*>(a)->do_it();

то это не сработает, все равно вызовется метод класса С — функция виртуальная.
int i;
i = (i++)+(i++);
cout << i;
Re[3]: Приведение типа указателя
От: NikeByNike Россия  
Дата: 05.03.08 10:42
Оценка: 3 (1)
Здравствуйте, Eldar9x, Вы писали:

E>если имелось ввиду

E>
E>A * a = new C();
E>dynamic_cast<A*>(a)->do_it();    
E>

E>то это не сработает, все равно вызовется метод класса С — функция виртуальная.

A * a = new C();
B * b = dynamic_cast<B*>(a);
if ( b )
{
    b->B::do_it();
}
Нужно разобрать угил.
Re: Приведение типа указателя
От: Bell Россия  
Дата: 05.03.08 10:48
Оценка:
Здравствуйте, Eldar9x, Вы писали:


E>Создаем объект класса и вызываем его метод:

E>
E>A * a = new C();
a->>do_it();
E>

E>получаем "c method"

E>получить указатель типа (A*) легко:

E>
E>A * a = new C();
a->>A::do_it(); // "a method"
E>


Выражение a->A::do_it(); не является приведением — это просто вызов функции с явной квалификацией.

E>или

E>
E>A * a = new C();
E>a = new A(*A)
a->>do_it(); // "a method"
E>


Это тоже сложно назвать приведением типа указателя — здесь просто напросто создается новый объект типа A (при этом предыдущий объект типа C, на который раньше указывал a, утекает).

E>Каким образом привести указатель к типу B*?


A * a = new C();
B* b = static_cast<B*>(a);//или dynamic_cast
b->do_it();//c method
b->B::bo_it();//b method
Любите книгу — источник знаний (с) М.Горький
Re[4]: Приведение типа указателя
От: Eldar9x  
Дата: 05.03.08 10:56
Оценка:
Здравствуйте, NikeByNike, Вы писали:
NBN>
NBN>A * a = new C();
NBN>B * b = dynamic_cast<B*>(a);
NBN>if ( b )
NBN>{
    b->>B::do_it();
NBN>}
NBN>


понятно. А возможно ли изменить тип указателя на тип B*, после операции:
A * a = new C();

так чтобы, вызов
a->do_it();

приводил к вызову метода класса B? Другими словами, сделать что-то вроде:
A * a = new C();
a = new B(*a);  // получили указатель типа B*, с данными от С, 
                // пусть это и чревато потерей данных.
                // Только сделать это не даст компилятор.
int i;
i = (i++)+(i++);
cout << i;
Re[5]: Приведение типа указателя
От: dotidot Россия  
Дата: 05.03.08 11:42
Оценка:
Здравствуйте, Eldar9x, Вы писали:

лучше озвучте суть проблемы, а то какие то технические детали вами придуманного способа её обхода обсуждаем.
Судя по описанию, можно предложить расширить интерфейс А(добавить doB), либо использовать костыли вроде State паттерна.

E>понятно. А возможно ли изменить тип указателя на тип B*, после операции:

E>
E>A * a = new C();
E>


Тип переменной в с++ менять нельзя никак. Можно далее в коде написать что-то вроде:
a = new B();

Если вам нужно полиморфное поведение, меняйте сам объект. Ну или изобретайте какой нить велосипед/городите враппер, что хуже решать вам.
вот пример недо State декоратора:

struct A
{
    virtual void f()=0;
    virtual ~A(){}
};

struct B:A
{
    virtual void f(){}
};

struct C:B
{
    virtual void f(){}    
};


struct StateFull:A
{
    C* c;
    bool callOnlyB;

    StateFull(C* c_):
        c(c_),
        callOnlyB(false)
    {}

    void setCallBOnly(bool b)
    {
        callOnlyB = b;
    }

    virtual void f()
    {
        if(callOnlyB)
            c->B::f();
        else
            c->f();
    }
}
Re[6]: Приведение типа указателя
От: Eldar9x  
Дата: 05.03.08 13:48
Оценка:
Здравствуйте, dotidot, Вы писали:

D>лучше озвучте суть проблемы


На самом деле, хотелось узнать, можно ли как то управлять вызовами виртуальных функций. То есть получается, что уже в момент создания объекта по указателю на базовый класс,
A *a = new C();

мы определяем, какая функция будет вызываться для этого объекта в иерархии классов.
А если вдруг окажется, что мне в какой-то момент понадобился вызов виртуальной функции не подразумевающегося класса, а скажем на шаг выше, или даже на два. Отсюда и возникает вопрос, возможно ли каким-то образом манипулировать указателем, уже после того, как объект создан, и тип его уже известен.
То есть, a->do_it() точно известно, что вызовется витуальная функция текущего типа класса.
Как вызвать для ЭТОГО объекта виртуальную функцию ОПРЕДЕЛЕННОГО класса, причем не меняя при этом никаких данных объекта, не меняя его тип итп?
int i;
i = (i++)+(i++);
cout << i;
Re[7]: Приведение типа указателя
От: Bell Россия  
Дата: 05.03.08 14:25
Оценка: 3 (1)
Здравствуйте, Eldar9x, Вы писали:

E>На самом деле, хотелось узнать, можно ли как то управлять вызовами виртуальных функций.

Можно. Есть 2 варианта:
1.неквалифицированный вызов a->do_it() — вызывается final overrider, т.е. в данном случае C::do_it.
2.квалифицированный вызов — вызывается указанный вариант:
B* b = new C();
b->A::do_it();
b->B::do_it()


E>То есть получается, что уже в момент создания объекта по указателю на базовый класс,

E>
E>A *a = new C();
E>

"создание объекта по указателю на базовый класс" — бесмыссленая фраза.

E>мы определяем, какая функция будет вызываться для этого объекта в иерархии классов.

Именно — объект имеет свой динамический тип, который невозможно изменить.

E>А если вдруг окажется, что мне в какой-то момент понадобился вызов виртуальной функции не подразумевающегося класса, а скажем на шаг выше, или даже на два. Отсюда и возникает вопрос, возможно ли каким-то образом манипулировать указателем, уже после того, как объект создан, и тип его уже известен.

Указателем манипулировать можно, но динамический тип объекта от этого не изменится.

E>То есть, a->do_it() точно известно, что вызовется витуальная функция текущего типа класса.

E>Как вызвать для ЭТОГО объекта виртуальную функцию ОПРЕДЕЛЕННОГО класса, причем не меняя при этом никаких данных объекта, не меняя его тип итп?
Используя явную квалификацию. Если, имея указатель типа A*, и зная, что этот указатель указывает на объект типа C, то для вызова метода B::do_it нужно проделать 2 операции:
1. преобразовать указатель типа A* в указатель типа B* или C*
2. вызвать требуемый метод, используя явную квалификацию
A* a = new C();
B* b = static_casz<B*>(a);
b->B::do_it();
C* c = static_cast<C*>(a);
c->B::do_it();
c->C::do_it();
Любите книгу — источник знаний (с) М.Горький
Re[8]: Приведение типа указателя
От: Eldar9x  
Дата: 05.03.08 14:36
Оценка:
Здравствуйте, Bell, Вы писали:
E>>Как вызвать для ЭТОГО объекта виртуальную функцию ОПРЕДЕЛЕННОГО класса, причем не меняя при этом никаких данных объекта, не меняя его тип итп?
B>Используя явную квалификацию. Если, имея указатель типа A*, и зная, что этот указатель указывает на объект типа C, то для вызова метода B::do_it нужно проделать 2 операции:
B>1. преобразовать указатель типа A* в указатель типа B* или C*
B>2. вызвать требуемый метод, используя явную квалификацию
B>
B>A* a = new C();
B>B* b = static_casz<B*>(a);
b->>B::do_it();
B>C* c = static_cast<C*>(a);
c->>B::do_it();
c->>C::do_it();
B>


Теперь все понятно, большое спасибо.
Насколько я понял, невозможно изменить указатель так, чтобы неквалифицированный вызов a->do_it() привел бы к вызову виртуальной функции какого-либо базового класса в иерархии.
int i;
i = (i++)+(i++);
cout << i;
Re[9]: Приведение типа указателя
От: Bell Россия  
Дата: 05.03.08 15:06
Оценка:
Здравствуйте, Eldar9x, Вы писали:

E>Теперь все понятно, большое спасибо.

E>Насколько я понял, невозможно изменить указатель так, чтобы неквалифицированный вызов a->do_it() привел бы к вызову виртуальной функции какого-либо базового класса в иерархии.

Правильно. Если do_it переопределена в наследниках, то будет вызван именно вариант из самого нижнего наследника, содержащего переопределение.

ЗЫ
Существует тонкость, связанная с вызовом (прямым или опосредованным) виртуальных функций в конструкторах/деструкторе. Тема эта время от времени всплывает, так что, возможно, тебе тоже будет интересно... Так что поиск в помощь
Любите книгу — источник знаний (с) М.Горький
Re: Приведение типа указателя
От: Erop Россия  
Дата: 06.03.08 00:08
Оценка:
Здравствуйте, Eldar9x, Вы писали:

E>Каким образом привести указатель к типу B*?


При желании извращаться бесконечно, можно извратиться довольно продвинутым способом.

Я так понимаю, что у тебя есть два метода, работающих с одиними и теми же данными. И тебе хочется так менять указатель, чтобы переключать какой из методов позавётся?

Я тогда немного переформулирую задачу:
class MyRealClass {
    // Тут данные и методы, в том числе
    void Method1();
    void Method2();
};


Теперь заводим интерфейс, через который мы планируем иметь доступ к нашему объекту:
struct IMyMethod {
    virtual ~IMyMethod() {};
    virtual void Method() = 0;
};


И заводим две его реализации:
struct MyMethod1 : IMyMethod, virtual MyRealClass {
    void Method() { Method1(); }
    IMyMethod* GetMethod1() { return this; }
};

struct MyMethod2 : IMyMethod, virtual MyRealClass {
    void Method() { Method2(); }
    IMyMethod* GetMethod2() { return this; }
};


Ну и теперь рожаем "переключаемый" объект
struct MyClass : MyMethod1, MyMethod2 { 
};


Ну, или, чуть удобнее:
class MyRealClass {
    // Тут данные и методы, в том числе
    void Method1();
    void Method2();
};

struct IMyMethod {
    virtual ~IMyMethod() {}
    virtual void Method() = 0;
    
    virtual IMyMethod* GetMethod1() = 0;
    virtual IMyMethod* GetMethod2() = 0;
};

struct MyMethod1 : IMyMethod, virtual MyRealClass {
    virtual void Method() { Method1(); }
};

struct MyMethod2 : IMyMethod, virtual MyRealClass {
    virtual void Method() { Method2(); }
};

struct MyClass : MyMethod1, MyMethod2 {
    virtual IMyMethod* GetMethod1() { return static_cast<MyMethod1*>( this ); }
    virtual IMyMethod* GetMethod2() { return static_cast<MyMethod2*>( this ); }
};


Ну и используем:
    IMyMethod* p = ( new MyClass )->GetMethod1();
    p->Method(); // Method1
    p = p->GetMethod2();
    p->Method(); // Method2
    delete p; // Всё хорошо, ничего не утекает :)
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[2]: Приведение типа указателя
От: Eldar9x  
Дата: 19.03.08 15:54
Оценка:
Здравствуйте, Erop, Вы писали:

спасибо, конечно, но такое решение для такого ничтожного вопроса, если честно, это как из пушки по воробьям.

Ну ответ уже найден:

E>Насколько я понял, невозможно изменить указатель так, чтобы неквалифицированный E>вызов a->do_it() привел бы к вызову виртуальной функции какого-либо базового класса E>в иерархии.


Правильно. Если do_it переопределена в наследниках, то будет вызван именно вариант из самого нижнего наследника, содержащего переопределение.

PS: Поздно отвечаю, конечно.
int i;
i = (i++)+(i++);
cout << i;
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.