воскресенье, 10 февраля 2013 г.

константные методы класса c++

Языки C# и Java унаследовали упрощенный вариант реализации неизменности из С++ [2, 3]. Можно пометить ссылку как константную (const в C#, final в Java), но метод пометить как константный нельзя. Поэтому компилятор контролирует использование только одной известной ему неконстантной операции - присвоения. Выгода от этого механизма минимальна и проявляется, в основном, при работе с примитивными типами в Java или value types в C#; как правило, объекты такого типа меняют состояние только с помощью операции присвоения.

Плюсы такой реализации: контроль физической неизменности на стадии компиляции. Минусы: наличие const_cast и mutable, решив проблему реализации логической неизменности, свело на нет усилия по обеспечению контроля обоих видов неизменности.

Увы, этот вариант имеет тот же недостаток, что и вариант с const_cast. Проверка со стороны компилятора ослабляется, не предлагая взамен никаких гарантий. Если, в силу ошибочной реализации, при каждом вызове метода Picture поле picture_data модифицируется, то это не будет обнаружено, а клиент станет каждый раз получать разные изображения одной и той же персоны.

Для ослабления контроля неизменности со стороны компилятора в С++ также используется модификатор mutable. Им можно пометить поля класса, значения которых разрешается менять в константных методах. Посмотрим, как будет выглядеть пример с использованием mutable (приведены только те элементы, которые изменены по сравнению с вариантом для const_cast).

Итак, проблема логической неизменности решена; метод Picture не меняет наблюдаемого состояния объекта. Однако одновременно потеряна защита со стороны компилятора, и теперь модификатор const не гарантирует, что состояние объекта остается неизменным.

Предположим, мы намерены оптимизировать работу метода Picture. Вовсе не обязательно, что во всех вариантах использования класса Personal будет требоваться получить фотографию, занимающую много памяти. Можно так реализовать метод Picture, чтобы фотография загружалась только в случае использования именно этого метода. Но в Picture нельзя менять переменную picture_data, которая хранит ссылку на фотографию: компилятор запрещает менять поля объекта в константном методе. Это и есть проблема логической неизменности. Пожалуй, самым распространенным случаем возникновения несоответствия логической и физической неизменности является кэширование данных и создание прокси-объектов [9]. Таким образом, в С++ модификатор const означает лишь физическую неизменность. Чтобы обеспечить возможность реализации логически константных методов, в С++ имеется конструкция const_cast. С ее помощью приведенный пример можно переписать так:

В примере методы Name, Age, Picture объявлены константными. Кроме того, можно наблюдать и использование константных ссылок: параметр методов SetName и SetPicture, возвращаемое значение методов Name и Picture. Компилятор обеспечит проверку того, что реализация константных методов не имеет побочных эффектов в виде изменения состояния объекта, реализующего интерфейс Personal. Как только обнаружится попытка выполнить запрещенную операцию, компилятор сообщит об ошибке.

Язык С++ позволяет пометить метод как константный [1]; неконстантные методы этого объекта запрещается использовать в теле помеченного метода, и в контексте этого метода ссылки на сам объект и все его поля константны. Также существует возможность пометить ссылку (указатель) как константную. Применительно к ссылке свойство константности означает, что через эту ссылку можно вызывать только константные методы; присвоение константной ссылки неконстантной запрещено. Проверку этих условий обеспечивает компилятор. Для обозначения константности используется модификатор const. Пример интерфейса с константными методами:

Выявить описанные проблемы очень непросто, поскольку выполняются совсем не те действия, которые ожидает программист. На поиски подобных ошибок можно потратить долгие часы. Между тем, при правильной поддержке неизменности, перечисленные проблемы могут быть предотвращены. Рассмотрим методы решения проблемы контроля неизменности, применяемые в различных языках программирования.

В таком случае две последние строки из примера содержали бы ошибку, так как во время вызова метода get() сохраняемый объект удалялся бы из объекта типа auto_ptr, и при его дальнейшем использовании возникало бы обращение к нулевому указателю.

Допустим, метод get() объекта auto_ptr не объявлен как неизменный. В этом случае была бы возможна ошибочная реализация:

После выполнения операции присвоения одного объекта другому, вполне естественно ожидать их равенства, но в данном случае равенства не будет. Объект first_string не будет содержать объект типа string, которым был проинициализирован: при выполнении операции присвоения он был удален из first_string. В результате, текст "some text" будет напечатан только один раз в последней строке. operator=() обладает определенной семантикой; в частности, предполагается, что копируемый объект остается неизменным. Однако в данном случае выполняются действия, которые не совпадают с этой семантикой. Если бы интерфейс был описан верно (параметр rhs метода operator=() был неизменным), то подобная ошибочная реализация была бы невозможна. Вариант такого решения предлагает библиотека STL, поставляемая с компилятором Visual C++ 6.0; он также является хорошим примером несовпадения логической и физической неизменности.

Метод operator=() вместо операции присвоения выполняет операцию переноса указателя на объект, что позволяет написать вот такой странный код:

Если не контролировать свойство неизменности, то его обеспечение будет целиком зависеть от квалификации программиста. Если же "неизменный" метод в процессе выполнения будет производить посторонние эффекты, то результат может быть самым неожиданным; отлаживать и поддерживать такой код очень тяжело. В качестве примера можно рассмотреть часть кода из библиотеки STL, поставляемой с компилятором Borland C++ 5.01 (файл memory.h):

Необходимость контроля неизменности

Говорят, что метод объекта обладает свойством неизменности (константности), если после его выполнения состояние объекта не изменяется. Различают два вида неизменности, логическую и физическую. Физическая неизменность означает полную идентичность объекта, с точностью до каждого поля, а логическая - его наблюдаемую идентичность (невидимые внешнему миру поля private и protected могут быть изменены, но это не должно быть заметно для клиента, использующего объект).

Объектно-ориентированное программирование сегодня изучено достаточно глубоко и существует множество поддерживающих эту парадигму языков, однако так называемая проблема обеспечения неизменности (immutability) везде решается уникальным образом, причем большинству реализаций присущи те или иные недостатки. Статья представляет обзор имеющихся решений и предлагает вариант реализации, свободный от ряда недостатков.

Свойство неизменности: ООП под микроскопом

Море(!) аналитической информации!

Свойство неизменности: ООП под микроскопом

Комментариев нет:

Отправить комментарий