Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Чтобы до конца понять суть описанной проблемы, рассмотрим следующую (некорректную) программу.
// Ошибка, генерируемая при возврате объекта из функции. #include < iostream> #include < cstring> #include < cstdlib> using namespace std; class sample { char *s; public: sample() { s = 0; } sample(const sample & ob); // конструктор копии ~sample() { if(s) delete [] s; cout < < " Освобождение s-памяти."; } void show() { cout < < s < < " "; } void set(char *str); }; // Конструктор копии. sample:: sample(const sample & ob) { s = new char[strlen(ob.s) +1]; strcpy(s, ob.s); } // Загрузка строки. void sample:: set(char *str) { s = new char[strlen(str) +1]; strcpy(s, str); } // Эта функция возвращает объект типа sample. Sample input() { char instr[80]; sample str; cout < < " Введите строку: "; cin > > instr; str.set(instr); return str; } Int main() { sample ob; // Присваиваем объект, возвращаемый // функцией input(), объекту ob. ob = input(); // Эта инструкция генерирует ошибку!!!! ob.show(); return 0; } Возможные результаты выполнения этой программы выглядят так. Введите строку: Привет Освобождение s-памяти. Освобождение s-памяти. Здесь " мусор" Освобождение s-памяти. В зависимости от используемого компилятора, вы можете увидеть " мусор" или нет. Программа может также сгенерировать ошибку во время выполнения. В любом случае ошибки не миновать. И вот почему. В этой программе конструктор копии корректно обрабатывает возвращение объекта функцией input(). Вспомните, что в случае, когда функция возвращает объект, для хранения возвращаемого ею значения создается временный объект. Поскольку при создании объекта-копии конструктор копии выделяет новую область памяти, член s исходного объекта и член s объекта-копии будут указывать на различные области памяти, которые, следовательно, не станут портить друг друга. Однако ошибки не миновать, если объект, возвращаемый функцией, присваивается объекту ob, поскольку при выполнении присваивания по умолчанию создается побитовая копия. В данном случае временный объект, возвращаемый функцией input(), копируется в объект ob. В результате член ob.s указывает на ту же самую область памяти, что и член s временного объекта. Но после присваивания в процессе разрушения временного объекта эта память освобождается. Следовательно, член ob.s теперь будет указывать на уже освобожденную память! Более того, память, адресуемая членом ob.s, должна быть освобождена и по завершении программы, т.е. во второй раз. Чтобы предотвратить возникновение этой проблемы, необходимо перегрузить оператор присваивания так, чтобы объект, располагаемый слева от оператора присваивания, выделял собственную область памяти. Реализация этого решения показана в следующей откорректированной программе. // Эта программа работает корректно. #include < iostream> #include < cstring> #include < cstdlib> using namespace std; class sample { char *s; public: sample(); // обычный конструктор sample(const sample & ob); // конструктор копии ~sample() { if(s) delete [] s; cout < < " Освобождение s-памяти."; } void show() { cout < < s < < " "; } void set(char *str); sample operator=(sample & ob); // перегруженный оператор присваивания }; // Обычный конструктор. Sample:: sample() { s = new char('0'); // Член s указывает на null-строку. } // Конструктор копии. sample:: sample(const sample & ob) { s = new char[strlen(ob.s)+1]; strcpy(s, ob.s); } // Загрузка строки. void sample:: set(char *str) { s = new char[strlen(str)+1]; strcpy(s, str); } // Перегрузка оператора присваивания. sample sample:: operator=(sample & ob) { /* Если выделенная область памяти имеет недостаточный размер, выделяется новая область памяти. */ if(strlen (ob.s) > strlen(s)) { delete [] s; s = new char[strlen(ob.s)+1]; } strcpy(s, ob.s); return *this; } // Эта функция возвращает объект типа sample. Sample input() { char instr[80]; sample str; cout < < " Введите строку: "; cin > > instr; str.set(instr); return str; } Int main() { sample ob; // Присваиваем объект, возвращаемый // функцией input(), объекту ob. ob = input(); // Теперь здесь все в порядке! ob.show(); return 0; } Эта программа теперь отображает такие результаты (в предположении, что на приглашение " Введите строку: " вы введете " Привет" ). Введите строку: Привет Освобождение s-памяти. Освобождение s-памяти. Освобождение s-памяти. Привет Освобождение s-памяти. Как видите, эта программа теперь работает корректно. Вы должны понимать, почему выводится каждое из сообщений " Освобождение s-памяти. " . (Подсказка: одно из них вызвано инструкцией delete в теле операторной функции operator=().) Перегрузка оператора индексации массивов ([]) В дополнение к традиционным операторам C++ позволяет перегружать и более " экзотические", например, оператор индексации массивов ([]). В C++ (с точки зрения механизма перегрузки) оператор " []" считается бинарным. Его можно перегружать только для класса и только с использованием функции-члена. Вот как выглядит общий формат операторной функции-члена operator[](). тип имя_класса:: operator[](int индекс) { //... } Оператор " []" перегружается как бинарный оператор. Формально параметр индекс необязательно должен иметь тип int, но операторные функции operator[]() обычно используются для обеспечения индексации массивов, поэтому в общем случае в качестве аргумента этой функции передается целочисленное значение. Предположим, у нас определен объект ob, тогда выражение ob[3] преобразуется в следующий вызов операторной функции operator[](): ob.operator[](3) Другими словами, значение выражения, заданного в операторе индексации, передается операторной функции operator[]() в качестве явно заданного аргумента. При этом указатель this будет указывать на объект ob, т.е. объект, который генерирует вызов этой функции. В следующей программе в классе atype объявляется массив для хранения трех int-значений. Его конструктор инициализирует каждый член этого массива. Перегруженная операторная функция operator[]() возвращает значение элемента, заданного его параметром. // Перегрузка оператора индексации массивов #include < iostream> using namespace std; const int SIZE = 3; class atype { int a[SIZE]; public: atype() { register int i; for(i=0; i< SIZE; i++) a[i] = i; } int operator[](int i) {return a[i]; } }; Int main() { atype ob; cout < < ob[2]; // отображает число 2 return 0; } Здесь функция operator[]() возвращает значение i-го элемента массива a. Таким образом, выражение ob[2] возвращает число 2, которое отображается инструкцией cout. Инициализация массива a с помощью конструктора (в этой и следующей программах) выполняется лишь в иллюстративных целях. Можно разработать операторную функцию operator[]() так, чтобы оператор " []" можно было использовать как слева, так и справа от оператора присваивания. Для этого достаточно указать, что значение, возвращаемое операторной функцией operator[](), является ссылкой. Эта возможность демонстрируется в следующей программе. // Возврат ссылки из операторной функции operator()[]. #include < iostream> using namespace std; const int SIZE = 3; class atype { int a[SIZE]; public: atype() { register int i; for(i=0; i< SIZE; i++) a[i] = i; } int & operator[](int i) {return a[i]; } }; Int main() { atype ob; cout < < ob[2]; // Отображается число 2. cout < < " "; ob[2] = 25; // Оператор " []" стоит слева от оператора " =". cout < < ob[2]; // Теперь отображается число 25. return 0; } При выполнении эта программа генерирует такие результаты. 2 25 Поскольку функция operator[]() теперь возвращает ссылку на элемент массива, индексируемый параметром i, оператор " []" можно использовать слева от оператора присваивания, что позволит модифицировать любой элемент массива. (Конечно же, его по-прежнему можно использовать и справа от оператора присваивания.) Одно из достоинств перегрузки оператора " []" состоит в том, что с его помощью мы можем обеспечить средство реализации безопасной индексации массивов. Как вы знаете, в C++ возможен выход за границы массива во время выполнения программы без Соответствующего уведомления (т.е. без генерирования сообщения о динамической ошибке). Но если создать класс, который содержит массив, и разрешить доступ к этому массиву только через перегруженный оператор индексации " []" , то возможен перехват индекса, значение которого вышло за дозволенные пределы. Например, следующая программа (в основу которой положен код предыдущей) оснащена средством контроля попадания в допустимый интервал. // Пример организации безопасного массива. #include < iostream> #include < cstdlib> using namespace std; const int SIZE = 3; class atype { int a[SIZE]; public: atype() { register int i; for(i=0; i< SIZE; i++) a[i] = i; } int & operator[] (int i); }; // Обеспечение контроля попадания в допустимый интервал для класса atype. int & atype:: operator [](int i) { if(i< 0 || i> SIZE-1) { cout < < " Значение индекса "; cout < < i < < " выходит за границы массива. "; exit(1); } return a[i]; } Int main() { atype ob; cout < < ob[2]; // Отображается число 2. cout < < " "; ob[2] =25; // Оператор " []" стоит в левой части. cout < < ob[2]; // Отображается число 25. ob[3] = 44; // Генерируется ошибка времени выполнения. // поскольку значение 3 выходит за границы массива. return 0; } Популярное:
|
Последнее изменение этой страницы: 2016-03-17; Просмотров: 1320; Нарушение авторского права страницы