Форум программистов, компьютерный форум CyberForum.ru

Наследование, dynamic_cast и использование последнего в связке с первым - C++

Восстановить пароль Регистрация
 
Gepar
 Аватар для Gepar
1173 / 529 / 20
Регистрация: 01.07.2009
Сообщений: 3,508
15.04.2012, 14:46     Наследование, dynamic_cast и использование последнего в связке с первым #1
Задание: написать отдалённую симуляцию интерфейса в виде консольного приложения, тоесть класс окно, в которое можно засовывать различные контролы (кнопки, едитбоксы и прочее). Всё это должно писаться через наследование, в общем-то всё работает, кроме одного пунктика: dynamic_cast на определённом этапе возвращает NULL, а без него всё работает хорошо. Беда в том что я могу писать только класс, а main будет какраз-таки с dynamic_cast'ом и его я изменить не могу так что должен под него подстраиваться.
Структура программы простая как доска, есть класс CControl от которого все контролы наследуются:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class CControl
{
public:
    //инициализация переменных с значениями координат элемента, их ID и заголовок (title)
    CControl(int _ID, double xPos1, double yPos1, double xPos2, double yPos2,  const string& _type, const string& _title="")
    :ID(_ID), type(_type), title(_title), x1(xPos1), y1(yPos1), x2(xPos2),y2(yPos2),printFromWindow(false),specialCBprint(false){}
 
 
    virtual void Print(ostream& os) const
    {
     //неважно
    }
 
    friend ostream& operator<<(ostream& os, const CControl& right)
    {
        right.Print(os);
        return os;
    }
 
    int ID;// our ID
    string type;// Button/Label/...
    string title;//"Ok" /"Cancel"/...
 
    //coordinats
    double x1;
    double y1;
    double x2;
    double y2;
};
Есть вот наследник с которым возникла проблема:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
class CInput: public CControl
{
public:
     //инициализация координат, ID и заголовка класса родителя
    CInput(int ID, double xPos1, double yPos1, double xPos2, double yPos2, const string& title)
    :CControl(ID, xPos1, yPos1, xPos2, yPos2, "Input", title) {}
    
    //возможность поменять заголовок (title). Специфичный метод, есть только у этого класса
    void SetValue(const string& val)
    {
        title= val;
    }
};
Вот другой наследник для примера чтобы ясно было как все остальные выглядят. Он попроще и с ним проблем нет так как никаких специфичных методов у него нет.
C++
1
2
3
4
5
6
class CButton: public CControl
{
public:
    CButton(int ID, double xPos1, double yPos1, double xPos2, double yPos2, const string& title)
    :CControl(ID, xPos1, yPos1, xPos2, yPos2, "Button", title) {}
};
И есть класс окно (CWindow) который помнит список всего чего в него понапихивали. Выглядит вот так:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class CWindow
{
private:
    //coordinats
    int fx1;
    int fy1;
    int fx2;
    int fy2;
 
    string title;
 
    //так я храню список всех контролов чтобы потом их возвращать при поиске
    //либо распечатывать
    struct ListControl
    {
        ListControl()
        :control(NULL), next(NULL){}
 
        ListControl(const CControl& cont)
        :control(new CControl(cont)), next(NULL) {}
 
        CControl* control;
        ListControl* next;
    } * head, *tail;
 
 
public:
    //конструктор по умолчанию, ничего особенного, проблема не в нём
    CWindow(const string& _title, int xPos1, int yPos1, int xPos2, int yPos2)
    :fx1(xPos1), fy1(yPos1), fx2(xPos2), fy2(yPos2), title(_title), countCB(0), head(NULL), tail(NULL) {}
 
 
    CWindow& Add(const CControl& obj)
    {
        if(! head)
        {
            head= tail= new ListControl(obj);
            Recalculate(head->control);//пересчитывает координаты в CControl (x1,x2,y1,y2), больше ничего не делает
        }
        else
        {
            ListControl* toAdd= new ListControl(obj);
            Recalculate(toAdd->control);//пересчитывает координаты в CControl (x1,x2,y1,y2), больше ничего не делает
            tail->next= toAdd;
            tail= toAdd;
        }
        return *this;
    }
 
    //поиск работает корректно, элемент с указанным ID он находит
    CControl* Search(int id) const
    {
        ListControl* temp= head;
        while(temp && temp->control->ID != id)
         temp= temp->next;
 
        return (temp ?  temp->control: NULL);
    }
};
И теперь проблемный main:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    CWindow a ( "Sample window", 10, 10, 600, 480 );
    a . Add ( CInput ( 11, 0.4, 0.1, 0.5, 0.1, "chucknorris" ) );
    a . Add ( CButton ( 1, 0.1, 0.8, 0.3, 0.1, "Ok" ) );
    
    //напоминаю что поиск работает корректно и он возвращает указатель на CControl с ID=11
    //CControl с ID=11 какраз и есть CInput
    CInput * il = dynamic_cast<CInput *> ( b . Search ( 11 ) ); // dynamic_cast вернёт NULL!!!!!!!!!!!!
    il -> SetValue ( "chucknorris@fit.cvut.cz" ); // crash crash crash!!!!!
 
 
   //Если попробовать так
   CInput* il= static_cast<CInput*> (b . Search ( 11 )); //всё ок, без проверок оно нормально приводиться
    il -> SetValue ( "chucknorris@fit.cvut.cz" );//вызовется и отработает корректно
 
 
   //Если попробовать так
   CInput* il= b . Search ( 11 );//всё ок, но компилятор выдаёт warning: invalid conversion from `CControl*' to `CInput*'|
   il -> SetValue ( "chucknorris@fit.cvut.cz" );//вызовется и отработает корректно
Что мне делать Как обмануть dynamic_cast и почему он вообще не справляется с задачей приведения типа?
Решение методу Search начать возвращать CInput не предлагать, помимо CInput я могу искать и CButton например, заранее ведь неизвестно что под тем ID будет храниться.

Могу выложить полный код в виде прикреплённого файла, но он несколько побольше (360 строк), я здесь всё почти выбросил кроме проблемного метода.

Добавлено через 19 минут
Проблема решена добавлением метода для добавления чисто CInput
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    CWindow& Add(const CInput& obj)
    {
        if(! head)
        {
            head= tail= new ListControl;
            head->control= new CInput(obj);
            Recalculate(head->control);
            head->next= NULL;
        }
 
        else
        {
            ListControl* toAdd= new ListControl;
            toAdd->control= new CInput(obj);
            toAdd->next= NULL;
            Recalculate(toAdd->control);
            tail->next= toAdd;
            tail= toAdd;
        }
        return *this;
    }
Иначе видать ещё на этапе добавления при вызове функции Add происходил срез данных (я правильно понял?) и все специфичные возможности CInput урезались после чего dynamic_cast не мог узнать этот контрол.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
15.04.2012, 14:46     Наследование, dynamic_cast и использование последнего в связке с первым
Посмотрите здесь:

dynamic_cast C++
dynamic_cast C++
C++ Для каждого Ai, начиная с последнего и заканчивая первым, вывести его квадратный корень не менее чем с четырьмя знаками после запятой
dynamic_cast C++
Частое использование dynamic_cast в конкретных целях. Правильно ли? C++
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
alex_x_x
бжни
 Аватар для alex_x_x
2441 / 1646 / 84
Регистрация: 14.05.2009
Сообщений: 7,163
15.04.2012, 15:06     Наследование, dynamic_cast и использование последнего в связке с первым #2
1) нет виртуальных деструкторов - очень плохо
2)
Цитата Сообщение от Gepar Посмотреть сообщение
a . Add ( CInput ( 11, 0.4, 0.1, 0.5, 0.1, "chucknorris" ) );
очень странно - по ссылке передается временный объект

Добавлено через 6 минут
Цитата Сообщение от Gepar Посмотреть сообщение
ListControl(const CControl& cont)
:control(new CControl(cont)), next(NULL) {}
все - в этом месте реальный тип объекта разрушается
чтобы такого не случалось CControl должен быть абстрактным
Gepar
 Аватар для Gepar
1173 / 529 / 20
Регистрация: 01.07.2009
Сообщений: 3,508
15.04.2012, 15:18  [ТС]     Наследование, dynamic_cast и использование последнего в связке с первым #3
Цитата Сообщение от alex_x_x Посмотреть сообщение
1) нет виртуальных деструкторов - очень плохо
Да есть они, просто спрятаны. Там много чего ещё есть, не хотел пугать количеством строк.

Цитата Сообщение от alex_x_x Посмотреть сообщение
все - в этом месте реальный тип объекта разрушается
чтобы такого не случалось CControl должен быть абстрактным
Попробую сейчас как-то это реализовать ...

Добавлено через 1 минуту
Цитата Сообщение от alex_x_x Посмотреть сообщение
очень странно - по ссылке передается временный объект
main не мой, такой будут подсовывать при тестировании, приходится приспосабливаться.
alex_x_x
бжни
 Аватар для alex_x_x
2441 / 1646 / 84
Регистрация: 14.05.2009
Сообщений: 7,163
15.04.2012, 15:22     Наследование, dynamic_cast и использование последнего в связке с первым #4
Цитата Сообщение от Gepar Посмотреть сообщение
main не мой, такой будут подсовывать при тестировании, приходится приспосабливаться.
странно, что компилятор это стерпел

Добавлено через 2 минуты
Цитата Сообщение от Gepar Посмотреть сообщение
ListControl(const CControl& cont)
:control(new CControl(cont)), next(NULL) {}
это работало бы, если в с++ была концепция виртуального конструктора,
но так как это не так, то первоначально нужно использовать указатель CControl*
Gepar
 Аватар для Gepar
1173 / 529 / 20
Регистрация: 01.07.2009
Сообщений: 3,508
15.04.2012, 15:34  [ТС]     Наследование, dynamic_cast и использование последнего в связке с первым #5
Цитата Сообщение от alex_x_x Посмотреть сообщение
все - в этом месте реальный тип объекта разрушается
чтобы такого не случалось CControl должен быть абстрактным
Не получается что-то, в общем смотрите как сейчас я решил проблему:
class CControl не абстрактный, от него унаследованы CComboBox, CInput, CButton и CLabel.
Для каждого из них пришлось добавить метод Add ( я понимаю что это плохо), тоесть
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
    CWindow& Add(const CComboBox& obj)
    {
        if(! head)
        {
            head= tail= new ListControl;
            head->control= new CComboBox(obj);
            Recalculate(head->control);
            head->next= NULL;
        }
 
        else
        {
            ListControl* toAdd= new ListControl;
            toAdd->control= new CComboBox(obj);
            toAdd->next= NULL;
            Recalculate(toAdd->control);
            tail->next= toAdd;
            tail= toAdd;
        }
 
        countCB++;
        return *this;
    }
 
 
    CWindow& Add(const CInput& obj)
    {
        if(! head)
        {
            head= tail= new ListControl;
            head->control= new CInput(obj);
            Recalculate(head->control);
            head->next= NULL;
        }
 
        else
        {
            ListControl* toAdd= new ListControl;
            toAdd->control= new CInput(obj);
            toAdd->next= NULL;
            Recalculate(toAdd->control);
            tail->next= toAdd;
            tail= toAdd;
        }
        return *this;
    }
 
    CWindow& Add(const CLabel& obj)
    {
         //считай тоже самое
        return *this;
     }
    CWindow& Add(const CButton& obj)
    {
     //считай тоже самое
        return *this;
    }
Напомню что список что помнит все контролы реализован так:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    struct ListControl
    {
        ListControl()
        :control(NULL), next(NULL){}
 
        ListControl(const CControl& cont)
        :control(new CControl(cont)), next(NULL) {}
 
        ListControl(const CComboBox& cont)
        :control(new CControl(cont)), next(NULL) {}
 
        CControl* control;
        ListControl* next;
    } * head, *tail;
Если я сделаю класс CControl полностью виртуальным -> я не смогу создавать объекты такого класса -> что же я буду делать при добавлении контролов?

Итого придётся убрать конструкторы у структуры ListControl:
C++
1
2
3
4
5
6
7
    struct ListControl
    {
        ListControl()
        :control(NULL), next(NULL){}
        CControl* control;
        ListControl* next;
    } * head, *tail;
А метод Add сделать таким что будет принимать ссылку на CControl:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    CWindow& Add(const CControl& obj)//я без понятия что мне здесь передали
    {
        if(! head)
        {
            head= tail= new ListControl; //конструктор по умолчанию CControl* control= NULL
            head->control= new CControl(obj);//ну а тут мне чего делать? Объект типа CControl создавать же нельзя теперь
            Recalculate(head->control);
            head->next= NULL;
        }
 
        else
        {
            ListControl* toAdd= new ListControl;
            toAdd->control= new CControl(obj);
            toAdd->next= NULL;
            Recalculate(toAdd->control);
            tail->next= toAdd;
            tail= toAdd;
        }
        return *this;
    }
Добавлено через 4 минуты
Цитата Сообщение от alex_x_x Посмотреть сообщение
но так как это не так, то первоначально нужно использовать указатель CControl*
Вот это не понял. Опишите подробнее пожалуйста.

Добавлено через 35 секунд
Цитата Сообщение от alex_x_x Посмотреть сообщение
странно, что компилятор это стерпел
даже предупреждением не пискнул.
alex_x_x
бжни
 Аватар для alex_x_x
2441 / 1646 / 84
Регистрация: 14.05.2009
Сообщений: 7,163
15.04.2012, 15:37     Наследование, dynamic_cast и использование последнего в связке с первым #6
Цитата Сообщение от Gepar Посмотреть сообщение
Если я сделаю класс CControl полностью виртуальным -> я не смогу создавать объекты такого класса -> что же я буду делать при добавлении контролов?
создавать контролы на heap'е
в список добавлять указатели, не сами объекты
иначе никак
динамический полиморфизм работает только на указателях и на ссылках (которые есть замаскированные указатели)
вы не можете внутри списка пересоздать объект сохранив тип, так как в с++ отсутствуют виртуальные конструкторы (разве только идиома прототипирования объектов)

Добавлено через 2 минуты
Цитата Сообщение от Gepar Посмотреть сообщение
даже предупреждением не пискнул.
http://codepad.org/3tER5fPO
может я чегото путаю, но конструирование объектов в таких случаях не происходит внутри вызова, так что с чему бы ему это позволять
Gepar
 Аватар для Gepar
1173 / 529 / 20
Регистрация: 01.07.2009
Сообщений: 3,508
15.04.2012, 15:42  [ТС]     Наследование, dynamic_cast и использование последнего в связке с первым #7
Определить тип в методе Add я почему-то тоже не могу( компилятор ругается)
C++
1
2
            if(dynamic_cast<const CLabel&>(obj))
            head->control= new CLabel(obj);
Да и разве это было бы решением десяток if(dynamic_cast<const тип&>(obj)) ...

Добавлено через 3 минуты
Цитата Сообщение от alex_x_x Посмотреть сообщение
может я чегото путаю, но конструирование объектов в таких случаях не происходит внутри вызова, так что с чему бы ему это позволять
Ну я не знаю, но ни vs 2010 ни minigw не пищит.
Цитата Сообщение от alex_x_x Посмотреть сообщение
создавать контролы на heap'е
в список добавлять указатели, не сами объекты
И какие же я указатели буду добавлять? Я же получаю const ссылку, а не просто указатель на объект. Я даже не знаю ссылка на что это(CLabel, CButton, CEditBox. Слава Богу хоть список типов мне известен и я не должен рассчитывать на то что кто-то допишет нового наследника от CControl да не надумает засунуть в моё окно, по крайней мере такого условия не было, вот если оно появиться ...).
Так что никакого другого решения кроме создания нового объекта на основе той ссылки что я получил и сохранения указателя на этот созданный объект в моём списке я не вижу, вы видите? Покажите пожалуйста в виде пары строк псевдо кода как это будет выглядеть.
alex_x_x
бжни
 Аватар для alex_x_x
2441 / 1646 / 84
Регистрация: 14.05.2009
Сообщений: 7,163
15.04.2012, 15:44     Наследование, dynamic_cast и использование последнего в связке с первым #8
Gepar, почему просто не добавлять указатели?
боитись, что их покоцают?

тогда можно так:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class CControl
{
public:
    //инициализация переменных с значениями координат элемента, их ID и заголовок (title)
    CControl(int _ID, double xPos1, double yPos1, double xPos2, double yPos2,  const string& _type, const string& _title="")
    :ID(_ID), type(_type), title(_title), x1(xPos1), y1(yPos1), x2(xPos2),y2(yPos2),printFromWindow(false),specialCBprint(false){}
    CControl* clone() = 0;
};
 
class CInput: public CControl
{
public:
     //инициализация координат, ID и заголовка класса родителя
    CInput(int ID, double xPos1, double yPos1, double xPos2, double yPos2, const string& title)
    :CControl(ID, xPos1, yPos1, xPos2, yPos2, "Input", title) {}
    
    CControl* clone() {
        return new CInput(...); // 
    }
};
 
CWindow& Add(const CControl& obj)
{
    CControl* control = obj.clone();
    //делай с control теперь что хочешь
}
Gepar
 Аватар для Gepar
1173 / 529 / 20
Регистрация: 01.07.2009
Сообщений: 3,508
15.04.2012, 16:27  [ТС]     Наследование, dynamic_cast и использование последнего в связке с первым #9
alex_x_x, интересное решение. Сейчас попробую реализовать.

Добавлено через 31 минуту
Всё получилось как и изначально планировалось (один метод Add и без всяких проверок типов, ну кроме одной чтобы считать CEditBox'ы как этого требует условие).
А почему если родительский класс не является виртуальным классом то приведение к его типу делало срез данных и собственно порчу информации о реальном типе класса?
alex_x_x
бжни
 Аватар для alex_x_x
2441 / 1646 / 84
Регистрация: 14.05.2009
Сообщений: 7,163
15.04.2012, 16:46     Наследование, dynamic_cast и использование последнего в связке с первым #10
Цитата Сообщение от Gepar Посмотреть сообщение
А почему если родительский класс не является виртуальным классом то приведение к его типу делало срез данных и собственно порчу информации о реальном типе класса?
ненене, для правильной работы dynamic_cast достаточно (впрочем как и необходимо) наличие как минимум одного виртуального метода (иначе не будет vtable со всеми вытекающими)
в твоем случае проблема была именно
Цитата Сообщение от alex_x_x Посмотреть сообщение
ListControl(const CControl& cont)
:control(new CControl(cont)), next(NULL) {}
Gepar
 Аватар для Gepar
1173 / 529 / 20
Регистрация: 01.07.2009
Сообщений: 3,508
16.04.2012, 21:29  [ТС]     Наследование, dynamic_cast и использование последнего в связке с первым #11
Цитата Сообщение от alex_x_x Посмотреть сообщение
наличие как минимум одного виртуального метода
Так у меня изначально была функция virtual print которую некоторые классы переопределяли, а некоторые нет. Вот так изначально выглядел CControl так что виртуальная таблица присутствовала же, ведь так?
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class CControl
{
public:
    CControl(int _ID, double xPos1, double yPos1, double xPos2, double yPos2,  const string& _type, const string& _title="")
    :ID(_ID), type(_type), title(_title), x1(xPos1), y1(yPos1), x2(xPos2),y2(yPos2),printFromWindow(false),specialCBprint(false){}
 
    //некоторые наследники её переопределяли, некоторые нет
    virtual void Print(ostream& os) const
    {
        if(printFromWindow==true)
         os<<"+- ";
 
        os<<"["<<ID<<"]"<<" "<<type<<" "
        <<"\""<<title<<"\" "
        <<"("<<x_1<<","<<y_1<<","
        <<x_2<<","<<y_2<<")"<<endl;
    }
 
    friend ostream& operator<<(ostream& os, const CControl& right)
    {
        right.Print(os);
        return os;
    }
 
    int ID;// our ID
    string type;// Button/Label
    string title;//"Ok" /"Cancel"
 
    //coordinats
    double x1;
    double y1;
    double x2;
    double y2;
 
    //real coordinats to print
    //setted by CWindow
    int x_1;
    int y_1;
    int x_2;
    int y_2;
 
    //+-
    bool printFromWindow;
 
    //|  +-
    bool specialCBprint;
 
    virtual ~CControl()
    {
 
    }
 
};
alex_x_x
бжни
 Аватар для alex_x_x
2441 / 1646 / 84
Регистрация: 14.05.2009
Сообщений: 7,163
16.04.2012, 22:12     Наследование, dynamic_cast и использование последнего в связке с первым #12
Gepar, объясни мне - с чего у тебя должно правильно кастоваться к твоему типу, если ты создаешь объекты типа new CControl(cont)?
Gepar
 Аватар для Gepar
1173 / 529 / 20
Регистрация: 01.07.2009
Сообщений: 3,508
17.04.2012, 02:03  [ТС]     Наследование, dynamic_cast и использование последнего в связке с первым #13
Цитата Сообщение от alex_x_x Посмотреть сообщение
Gepar, объясни мне - с чего у тебя должно правильно кастоваться к твоему типу, если ты создаешь объекты типа new CControl(cont)
Ну я же типа CControl'у пихал то объект не CControl'а, а его наследника уже Но тут, как уже писалось, не хватает виртуального конструктора.
silent_1991
Эксперт C++
4938 / 3014 / 149
Регистрация: 11.11.2009
Сообщений: 7,024
Завершенные тесты: 1
17.04.2012, 14:59     Наследование, dynamic_cast и использование последнего в связке с первым #14
Цитата Сообщение от Gepar Посмотреть сообщение
не хватает виртуального конструктора
Таковых не существует.
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
17.04.2012, 17:13     Наследование, dynamic_cast и использование последнего в связке с первым
Еще ссылки по теме:

C++ dynamic_cast
C++ Нахождение последнего элемента и перестановка его перед первым (списки)
Использование указателей, классы, наследование C++

Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
Gepar
 Аватар для Gepar
1173 / 529 / 20
Регистрация: 01.07.2009
Сообщений: 3,508
17.04.2012, 17:13  [ТС]     Наследование, dynamic_cast и использование последнего в связке с первым #15
Цитата Сообщение от silent_1991 Посмотреть сообщение
Таковых не существует.
Так потому же и не хватает что не существует их
Yandex
Объявления
17.04.2012, 17:13     Наследование, dynamic_cast и использование последнего в связке с первым
Ответ Создать тему
Опции темы

Текущее время: 05:49. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2016, vBulletin Solutions, Inc.
Рейтинг@Mail.ru