Мегаобучалка Главная | О нас | Обратная связь


Для чего используется вызов throw без аргументов?



2016-09-17 713 Обсуждений (0)
Для чего используется вызов throw без аргументов? 0.00 из 5.00 0 оценок





Ответ: Для повторного возбуждения предыдущего исключения и направления его следующему обработчику.
Пример:

 

try{ //.... try { // Call something } catch(const std::exception& ) { // Make/Check something.. throw; // Пересылаем исключение следующему обработчику } //...}catch(const std::exception& e){ std::cout << e.what() << std::endl;}

C++: конструктор копирования, оператор присваивания, деструктор

Конструктор копирования, деструктор и перегруженный оператор присваивания - незаменимые элементы каждого класса, работающего с динамически выделенной памятью.

Конструктор копирования

Конструктор копирования, в отличии от других, в качестве параметра принимает константную ссылку на объект класса.

// Прототип конструктора копированияKlass(const Klass &);

Данный конструктор вызывается всякий раз, когда создаётся новый объект и для его инициализации берётся значение существующего объекта того же типа. Например, в следующих случаях:

Klass k2(k1);Klass k3 = k2;Klass k4 = Klass(k3);Klass * pKlass = new Klass(k4);

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

В чём же проблема отсутствия конструктора копирования при выделении в классе динамической памяти? Дело в том, что при отсутствии явного описания, он описывается неявно. Неявный конструктор выполняет поверхностное копирование, т. е. просто дублирует биты из переменных. Таким образом, вместо данных из динамической памяти, копируется адреса на них. В результате, появляется несколько объектов, указывающих на одну область памяти. При изменении этой области через один объект, она также изменится и в другом, что в большинстве случаев является нежелательным поведением. Поэтому в классах, работающих с динамической памятью, необходимо всегда явно объявлять конструктор копирования (см. пример в конце). Как вариант исключения данной проблемы, можно поместить конструктор копирования в приватной области класса, что вовсе запретит выполнять копирование.

Перегруженная операция присваивания

Перегруженная операция присваивания используется при присваивании одного объекта другому существующему объекту. Здесь присутствует такая же проблема, что и в конструкторе копирования. К тому же, у объекта, которому присваивается значение, уже может быть выделена динамическая память. Перед присваиванием новых данных, выделенную ранее память необходимо очистить, чтобы не допустить её утечки (см. пример в конце). Также необходимо обработать случай самоприсваивания. В противном случае, данные в динамической памяти просто будут утеряны. Аналогично копированию, присваивание также можно запретить, поместив операцию в приватной области класса.

Деструктор

Деструктор вызывается перед удалением объекта и предназначен для освобождения всех используемых ресурсов. Чтобы не допустить утечки памяти, в деструкторе необходимо её очистить.

Пример

#include <iostream> struct Klass { int size; double * data; Klass() { std::cout << "Конструктор по умолчанию" << std::endl; size = 100; data = new double[size]; } Klass(const Klass & klass) { std::cout << "Конструктор копирования" << std::endl; size = klass.size; data = new double[size]; for (int i = 0; i < size; i++) { data[i] = klass.data[i]; } } Klass & operator=(const Klass & klass) { if (this != &klass) { std::cout << "Перегруженный оператор присваивания" << std::endl; delete[] data; size = klass.size; data = new double[size]; for (int i = 0; i < size; i++) { data[i] = klass.data[i]; } } else { std::cout << "Самоприсваивание" << std::endl; } return *this; } ~Klass() { std::cout << "Деструктор" << std::endl; delete[] data; }}; int main() { setlocale(LC_ALL, "Russian"); Klass k1; Klass k2(k1); Klass k3 = k2; Klass k4 = Klass(k3); Klass * pKlass = new Klass(k4); k1 = k2; k2 = k2;}

Стоить отметить, что во всех трёх функциях память должна выделяться и удаляться одинаковым образом. Т. е. нельзя в одном случае использовать delete, а в другом delete[].

Разработка → Приведение типов

Qt*

Будучи на конференции Qt Developer Days 2010 я узнал, что одним из самых популярных вопросов на собеседовании в разные зарубежные компании, работающие с Qt библиотекой, является вопрос о различиях в способах приведения типов в C++. Поэтому здесь я рассмотрю основные различия между static_cast, dynamic_cast, const_cast, reinterpret_cast, C-style cast, qobject_cast и qvariant_cast

 

1. static_cast.

Синтаксис:

 

TYPE static_cast<TYPE> (object);


Может быть использован для приведения одного типа к другому. Если это встроенные типы, то будут использованы встроенные в C++ правила их приведения. Если это типы, определенные программистом, то будут использованы правила приведения, определенные программистом.
static_cast между указателями корректно, только если один из указателей - это указатель на void или если это приведение между объектами классов, где один класс является наследником другого. То есть для приведения к какому-либо типу от void*, который возвращает malloc, следует использовать static_cast.

 

int * p = static_cast<int*>(malloc(100));

Если приведение не удалось, возникнет ошибка на этапе компиляции. Однако, если это приведение между указателями на объекты классов вниз по иерархии и оно не удалось, результат операции undefined. То есть, возможно такое приведение: static_cast<Derived*>(pBase), даже если pBase не указывает на Derived, но программа при этом будет вести себя странно.


static_cast преобразует выражения одного статического типа в объекты и значения другого статического типа. Поддерживается преобразование численных типов, указателей и ссылок по иерархии наследования как вверх, так и вниз. Проверка производится на уровне компиляции, так что в случае ошибки сообщение будет получено в момент сборки приложения или библиотеки.

2. dynamic_cast

Синтаксис:

 

TYPE& dynamic_cast<TYPE&> (object);

TYPE* dynamic_cast<TYPE*> (object);

 

Используется для динамического приведения типов во время выполнения. В случае неправильного приведения типов для ссылок вызывается исключительная ситуация std::bad_cast, а для указателей будет возвращен 0. Использует систему RTTI (Runtime Type Information). Безопасное приведение типов по иерархии наследования, в том числе для виртуального наследования.

 

Безопасное приведение по иерархии наследования, в том числе и для виртуального наследования.
dynamic_cast<derv_class *>(base_class_ptr_expr)
Используется RTTI (Runtime Type Information), чтобы привести один указатель на объект класса к другому указателю на объект класса. Классы должны быть полиморфными, то есть в базовом классе должна быть хотя бы одна виртуальная функция. Если эти условие не соблюдено, ошибка возникнет на этапе компиляции. Если приведение невозможно, то об этом станет ясно только на этапе выполнения программы и будет возвращен NULL.
dynamic_cast<derv_class &>(base_class_ref_expr)
Работа со ссылками происходит почти как с указателями, но в случае ошибки во время исполнения будет выброшено исключение bad_cast.


3. const_cast.

Синтаксис:

 

TYPE const_cast<TYPE> (object);

 

Пожалуй самое простое приведение типов. Снимает cv qualifiers — const и volatile, то есть константность и отказ от оптимизации компилятором переменной. Это преобразование проверяется на уровне компиляции и в случае ошибки приведения типов будет выдано сообщение.

Самое простое приведение типов. Убирает так называемые cv спецификаторы (cv qualifiers), то есть const и volatile. volatile встречается не очень часто, так что более известно как приведение типов, предназначенное для убирания const. Если приведение типов не удалось, выдается ошибка на этапе компиляции.
При использовании остальных приведений типов cv спецификаторы останутся как были.

const char *str = "hello";char *str1 = const_cast<char*>(str);

 


4. reinterpret_cast

Синтаксис:

 

TYPE reinterpret_cast<TYPE> (object);
Приведение типов без проверки. reinterpret_cast — непосредственное указание компилятору. Применяется только в случае полной уверенности программиста в собственных действиях. Не снимает константность и volatile. применяется для приведения указателя к указателю, указателя к целому и наоборот.

 

Самое нахальное приведение типов. Не портируемо, результат может быть некорректным, никаких проверок не делается. Считается, что вы лучше компилятора знаете как на самом деле обстоят дела, а он тихо подчиняется. Не может быть приведено одно значение к другому значению. Обычно используется, чтобы привести указатель к указателю, указатель к целому, целое к указателю. Умеет также работать со ссылками.

reinterpret_cast<whatever *>(some *)
reinterpret_cast<integer_expression>(some *)
reinterpret_cast<whatever *>(integer_expression)

Чтобы использовать reinterpret_cast нужны очень и очень веские причины. Используется, например, при приведении указателей на функции.
Что делает приведение типов в стиле С: пытается использовать static_cast, если не получается, использует reinterpret_cast. Далее, если нужно, использует const_cast .

 

Примеры
unsigned* и int* никак не связаны между собой. Есть правило приведения между unsigned (int) и int, но не между указателями на них. И привести их с помощью static_cast не получится, придется использовать reinterpret_cast. То есть вот так работать не будет:

 

unsigned* v_ptr;

cout << *static_cast<int*>(v_ptr) <<endl;

 

Приведение вниз по иерархии:

 

class Base { public: virtual ~Base(void) { } };

class Derived1 : public Base { };

class Derived2 : public Base { };

class Unrelated { };

 

Base* pD1 = new Derived1;

Вот такое приведение корректно: dynamic_cast<Derived1 *>(pD1);
А вот такое возвратит NULL: dynamic_cast<Derived2 *>(pD1);
Никак не связанные указатели можно приводить с помощью reinterpret_cast:

 

Derived1 derived1;

Unrelated* pUnrelated = reinterpret_cast<Unrelated*>(&derived1);


Пример использования static_cast:

 

int* pi;

void* vp = pi;

char* pch = static_cast<char*>(vp);


Примеры использования reinterpret_cast:

 

float f (float);

struct S {

float x;

float f (float);

} s;

void g () {

reinterpret_cast<int *>(&s.x);

reinterpret_cast<void (*) ()>(&f);

reinterpret_cast<int S::*>(&S::x);

reinterpret_cast<void (S::*) ()>(&S::f);

reinterpret_cast<void**>(reinterpret_cast<long>(f));

}


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

 

string sHello("Hello");

(void)sHello.size(); // Throw away function return

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


5. C-style cast

Синтаксис:

 

TYPE (TYPE*) object;

 

Си-шный метод приведения типов. Пожалуй самый нежелательный способ приведения типов. Страуструп пишет:
«Например, что это значит выражение — x = (T)y;. Мы не знаем. Это зависит от типа T, типов x и y. T может быть названием типа, typedef или может быть параметр template-а. Может быть, х и у являются скалярными переменными и Т представляет собой значение преобразования. Может быть, х объекта класса, производного от класса Y и Т — нисходящее преобразование. По этой причине программист может не знать, что он делает на самом деле.»
Вторая причина нежелательного использования приведения типов в C-style — трудоемкость процесса поиска мест приведения типов.

С-style cast

В языке С явное приведение типов осуществялось при заключении типа в круглые скобки, так называемое "приведение в круглых скобках"

Например, преобразование Double --> int:

int i = (int) 2.5;

Однако, такая форма приведения не наглядна, что существенно затрудняет поиск ошибок

Кроме того могут возникать ошибки при приведении пользовательских типов:

A * a = ...; B * b = (B *) a;

Для некоторых типов могут возникать неопределенности. A и B могут быть никак не связаны, т.е. эта операция может не быть правомощной.

Надо различать преобразования связанных типов и несвязанных типов.



2016-09-17 713 Обсуждений (0)
Для чего используется вызов throw без аргументов? 0.00 из 5.00 0 оценок









Обсуждение в статье: Для чего используется вызов throw без аргументов?

Обсуждений еще не было, будьте первым... ↓↓↓

Отправить сообщение

Популярное:
Почему двоичная система счисления так распространена?: Каждая цифра должна быть как-то представлена на физическом носителе...
Как вы ведете себя при стрессе?: Вы можете самостоятельно управлять стрессом! Каждый из нас имеет право и возможность уменьшить его воздействие на нас...



©2015-2024 megaobuchalka.ru Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. (713)

Почему 1285321 студент выбрали МегаОбучалку...

Система поиска информации

Мобильная версия сайта

Удобная навигация

Нет шокирующей рекламы



(0.01 сек.)