Рассылка закрыта
При закрытии подписчики были переданы в рассылку "Программирование на 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 |
Отписаться
Убрать рекламу |
| В избранное | ||
