Рассылка закрыта
При закрытии подписчики были переданы в рассылку "Программирование на Visual С++" на которую и рекомендуем вам подписаться.
Вы можете найти рассылки сходной тематики в Каталоге рассылок.
C++ для всех
Информационный Канал Subscribe.Ru |
C++ для всех. Выпуск 9 Приведение типов |
Здравствуйте, уважаемые подписчики
Сегодня разговор пойдет о способах приведения типов, распространенных в языке С++. Итак, приведение типов как таковое может происходить явно и неявно, причем за неявное приведение типов отвечает компилятор со всеми вытекающими последствиями. Рассмотрим пару примеров неявного приведения:
class A { public: A(bool b=true){ printf("A(bool b=true)\n"); } }; int main() { A a; a = 10; char i; double d = 1000; i = d;//усечение значения return 0; }
На экране после выполнения видим:
A(bool b=true) A(bool b=true)
Т.е. в данном случае компилятор сгенерировал такой код:
A a; a = A(bool(10));
Цифра 10 в этом примере приведена только для того, чтобы показать, что компилятор к некоторым типам может преобразовывать почти все. Существуют определенные правила неявного приведения типов, вкратце скажем так - любые численные и булевые типы могут неявно преобразовываться один в другой, при преобразовании возможна потеря величины значений (усечение), например в случае приведения int к char или int к uint. Как правило, компиляторы выдают предупреждения при возможном некорректном приведении чисел. Булевым типам соответствуют такие числовые значения: true - 1, false - 0.
Бывают случаи, когда необходимо обезопасить себя от возможного случайного создания временных объектов при приведении типов. В этом случае при объявлении конструктора класса необходимо использовать ключевое слово explicit:
class A { public: explicit A(bool b=true){ printf("A(bool b=true)\n"); } }; int main() { A a; a = 10; //ошибка компиляции a = A(10); //нормально return 0; }
Кстати, большинство печатающихся программистов рекомендуют explicit использовать как можно чаще.
Теперь поговорим о явном приведении. Явное приведение используется в тех случаях, когда или компилятор не может привести тип самостоятельно или необходимо использовать конкретный тип в зависимости от контекста задачи.
С приведением объектов все понятно,- если тип объекта не соответствует требуемому, то вызывается соответствующий конструктор, нас же больше интересует приведение указателей и ссылок в иерархии объектов, т.к. именно здесь часто возникает масса проблем и ошибок.
В стиле языке С для явного приведения указателя к требуемому типу используется такая форма записи:
class A{}; class B : public A{}; class C : public B{}; int main() { A *pa = new C; C *pc = (C*)pa; B *pb = (B*)pa; ... delete (C*)pa; return 0; }
Проблема в том, что для подобных случаев мы обязаны либо четко знать, что за тип мы имеем либо вводить дополнительный идентификатор типа, - первый случай чреват ошибками, второй - неудобен. Именно поэтому подобное явное приведение крайне не рекомендуется к использованию в языке С++ и применяется в исключительных случаях вроде приведения типа void*.
В С++ существую более безопасные механизмы приведения типов.
Сразу выделим два варианта явного приведения - динамическое приведение и статическое.
Статическое приведение выполняется на этапе компиляции и не требует временных (процессорных) затрат во время выполнения программы. Если откреститься от самого механизма приведения (который проверяет допустимость приведения в тот или иной тип), то в результат мы получаем все то же эффективное С-приведение.
Динамическое приведение требует наличия дополнительных, идентифицирующих тип, структур.
В С++ существует несколько операторов приведения типов: static_cast, const_cast, dynamic_cast, reinterpret_cast. Опишем каждый из них подробнее.
static_cast
Фактически, для большинства случаев там, где Вы применяли (type)expression следует писать const_cast<type>(expression):
int main() { int v; double d; d = static_cast<double>(v); return 0; }
Но на static_cast накладываются и ограничения, например нельзя снять с выражения константность - для этого существует оператор const_cast.
const_cast
Данный оператор специально предназначен для снятия константности с выражения, т.е. можно делать так:
struct STest{ int key; char str[128]; }; const STest* getValue(int key){ STest *ret; ... return ret; } int main() { STest *p = const_cast<STest*>(getValue(10)); if(p) strcpy(p->str,"Hello"); return 0; }
При использовании оператора const_cast важно понимать, что снятие константности не меняет логики программы, поэтому если функция возвращает константное выражение, то предполагается, что его нельзя изменять. В приведенном примере подразумевается, что член структуры STest::key является немодифицируемым ключем, а STest::str изменять можно.
dynamic_cast
Данный оператор предназначен для динамической идентификации типов и как правило, применяется в иерархии классов. Смотрим пример:
class A { public: virtual ~A(){} void a(){ printf("A::a()\n"); } }; class B : public A { public: void b(){ printf("B::b()\n"); } }; class C : public B { public: void c(){ printf("C::c()\n"); } }; int main() { A *pa = new C; if(A *p=dynamic_cast<A*>(pa)) p->a(); if(B *p=dynamic_cast<B*>(pa)) p->b(); if(C *p=dynamic_cast<C*>(pa)) p->c(); delete pa; return 0; }
На экране видим:
A::a() B::b() C::c()
А теперь несколько изменим функцию main:
int main() { A *pa = new B; if(A *p=dynamic_cast<A*>(pa)) p->a(); if(B *p=dynamic_cast<B*>(pa)) p->b(); if(C *p=dynamic_cast<C*>(pa)) p->c(); delete pa; return 0; }
На экране видим:
A::a() B::b()
Нельзя применять dynamic_cast для константных выражений:
... int main() { A *pa = new B; const A *pa1 = pa; A *p=dynamic_cast<A*>(pa1); //ошибка компиляции const A *p=dynamic_cast<const A*>(pa1); //нормально delete pa; return 0; }
reinterpret_cast
Данный оператор настоятельно не рекомендуется использовать, т.к. он является платформозависимым, но знать что он делает нужно. reinterpret_cast оперирует внутренним представлением объекта, причем вся ответственность за корректность применения ложится на программиста. По-сути, это эквивалент явному преобразованию в стиле С, только называется по другому - именно это название и служит для того, чтобы программист внимательно контролировал преобразование. Посмотрите на пример ошибочного использования reinterpret_cast:
int main() { int *val; char *pc = reinterpret_cast<char*>(val); std::string s(pc); //неопределенное поведение return 0; }
Фактически, вышеприведенный пример эквивалентен следующему:
int main() { int *val; char *pc = (char*)val; std::string s(pc); //неопределенное поведение return 0; }
Подведем итог. Использование операторов приведения типов не является спасением от небрежного программирования, однако позволяет в большинстве случаев сделать поиск ошибки более простым, кроме того, часть ошибок приведения может быть обнаружена еще на этапе компиляции программы. Поэтому, постарайтесь отказаться от приведения типов в стиле C,- используйте вместо этого вышеописанные операторы.
Практическое занятие
Регулярно стали поступать предложения написать что-нибудь полезное с использованием ранее описанного материала. Не могу с этим не согласиться. Возник только вопрос: что писать? Так как данная рассылка не является платформенно зависимой, соответственно и писать мы будем не зависящее от операционных систем.
Всем нам в своих программах приходится сохранять и загружать различные параметры, часто возникает проблема передачи неопределенного числа параметров в функции. Именно для решения этой и подобных проблем мы напишем один класс.
Итак, постановка задачи. Наш класс должен хранить в себе иерархичный список параметров. Уровень вложенности параметров не ограничен. Графически структура параметров выглядит так:
<val1> <val2> name=www ddd=1212 dddfff=1091029 <valx> ccc=10 </valx> </val2> </val1> <val2> name=1111111 vvv=111 </val2>
Короче, структура хранения данных чем-то напоминает xml-файл. Строка поиска и инициализации, например параметра ссс будет выглядеть так:
val1.val2.valx.ccc=
Так как число вложенных уровней не ограничено, целесообразно определить базовый класс с древовидной структурой:
---tree.h---start------------------------------------------------------------------------------------------------- #ifndef TREE_H #define TREE_H template<class T> class TTree { public: class Item { friend class TTree; public: Item(Item &to_node,T *d,bool as_last=false) :ttree(to_node.ttree),data(d),echild(0),eparent(&to_node) { ++(ttree->count_i); if(as_last){ eprev = to_node.echild; if(eprev) while(eprev->enext) eprev=eprev->enext; if(eprev) eprev->enext = this; else to_node.echild = this; enext = 0; } else{ eprev = 0; enext = to_node.echild; to_node.echild = this; } if(enext) enext->eprev = this; } Item(TTree &tr,Item *after,T *d) :ttree(&tr),data(d),echild(0),eprev(after) { ++(ttree->count_i); if(after){ eparent = after->eparent; enext = after->enext; if(enext) enext->eprev = this; after->enext = this; } else{ eparent = 0; enext = tr.first_e; if(enext) enext->eprev = this; tr.first_e = this; } } ~Item() { if(eprev) eprev->enext = enext; else if(this == ttree->first_e) ttree->first_e = enext; if(enext) enext->eprev = eprev; if(eparent && eparent->echild==this) eparent->echild = enext; if(ttree->autoDelete()) delete data; Item *p; while(echild){ p = echild; delete p;//для предотвращения возможного обнуления указателя } --(ttree->count_i); } bool isNode()const{ return echild!=0; } Item* firstChild()const{ return echild; } Item* prev()const{ return eprev; } Item* next()const{ return enext; } Item* parent()const{ return eparent; } TTree* tree()const{ return ttree; } T* getData()const{ return data; } void setData(T *d){ data=d; } T* operator->()const{ return data; } void moveItem(Item &after) { if(&after!=this){ if(eparent && (eparent->echild==this)) eparent->echild = enext; if(eprev) eprev->enext = enext; if(enext) enext->eprev = eprev; if(ttree->first_e==this) ttree->first_e = enext; enext = after.enext; if(enext) enext->eprev = this; after.enext = this; eprev = &after; eparent = after.eparent; } } private: Item *eparent; Item *echild; Item *eprev; Item *enext; T *data; TTree *ttree; }; TTree(bool ad=false):auto_delete(ad),first_e(0),count_i(0){} virtual ~TTree(){ clear(); } int count()const{ return count_i; } void setAutoDelete(bool ad){ auto_delete = ad; } bool autoDelete()const{ return auto_delete; } Item* firstItem()const{ return first_e; } Item* lastItem(Item *item=0)const{ if(!item){ if(!first_e) return 0; item=first_e; } while(item->enext) item = item->enext; return item; } Item* findItem(const T *d,const Item *from=0)const{ return finditem(from ? from : first_e,d); } bool itemExists(const Item *item)const{ return itemexists(first_e,item); } virtual void clear(); protected: Item* finditem(const Item *ret,const T *d)const{ Item *item; while(ret){ if(ret->data==d) return ret; item = finditem(ret->echild,d); if(item) return item; ret = ret->enext; } return ret; } bool itemexists(const Item*,const Item*)const; private: Item *first_e; int count_i; bool auto_delete; }; //------------------------------------------------------------------------- template<class T> inline void TTree<T>::clear() { Item *p; while(first_e){ p = first_e; delete p; } } template<class T> bool TTree<T>::itemexists(const Item *item,const Item *find)const { while(item){ if(item==find) return true; if(itemexists(item->echild,find)) return true; item = item->enext; } return false; } #endif //TREE_H ---tree.h---end---------------------------------------------------------------------------------------------------
Данный класс объявлен шаблонным, поэтому хранить в виде древовидной иерархии мы можем все что угодно. Сохраните этот текст в отдельный файл, в следующем выпуске мы займемся непосредственно реализацией механизма сохранения, загрузки и модификации произвольных параметров.
На сегодня все. Пожелания, предложения, вопросы прошу на iqsoft@cg.ukrtel.net
В следующем выпуске - исключения
(с) Юрий Гордиенко
http://subscribe.ru/
E-mail: ask@subscribe.ru |
Отписаться
Убрать рекламу |
В избранное | ||