|
Программирование >> Полиморфизм без виртуальных функций в с++
хранится сумма al+a2) в стек, а какая-то другая функция попытается затем воспользоваться им, сняв его со стека уже после того, как произошел возврат из f () и, стало быть, временный объект уничтожен; □ иногда временный объект живет слишком долго. Например, что произойдет, если X - тип матрицы размером 1000x1000, а в блоке создается несколько десятков временных матриц? Так можно исчерпать даже довольно большую физическую память и заставить механизм виртуальной памяти постоянно подкачивать страницы. По-моему, в реальных программах первая проблема встречается редко и общее решение дает автоматическая сборка мусора (см. раздел 10.7). Но вот вторая весьма распространена и серьезна. Некоторые пользователи вынуждены решать ее, заключая в отдельный блок каждое предложение, в котором есть вероятность создания временных объектов: void f(X al, X а2) { extern void g(const X&); X z; ... {z = al+a2;} {g(al+a2);} ... Если точка уничтожения расположена в конце блока (в Cfront так оно и есть), то у пользователей хотя бы остается возможность явно обойти проблему. Однако некоторые требовали более удобного ее решения. Поэтому в ARM это правило было ослаблено, и объект мог теперь уничтожаться в любой точке между первым его использованием и концом блока. Но, поскольку в разных компиляторах время жизни объекта устанавливалось по-разному, ситуация не стала лучше. Поэтому невозможно было написать гарантированно переносимую программу, если только не предполагать, что временный объект уничтожается немедленно. Это решение быстро признали неприемлемым, поскольку оно было несовместимо с некоторыми общеупотребительными в С идиомами, например: class String { ... public: friend String operator+(const String&, const Strings); ... operator const char*(); С-строка void f(String sl, String s2) { printf( %s , (const char*)(sl+s2)); ... Для создания С-строки, передаваемой для печати функции printf, используется конвертор из класса String. В типичной реализации конвертор просто возвращает указатель на часть объекта String. При такой простой реализации конвертора этот пример не будет работать, если применяется немедленное уничтожение временных объектов . Происходит вот что: создается временный объект для хранения sl+s2, указатель на внутреннюю область этого объекта передается конвертору в С-строку, временный объект уничтожается, а уже после этого указатель на уничтоженный объект передается printf (). Но деструктор временного объекта класса String освободил память, где находилась С-строка. Такой код распространен широко, и даже те реализации, в которых обычно выдержана стратегия немедленного уничтожения, например GNU C-1-1-, стараются в подобных случаях отложить его. Размышления в это.м русле натолкнули меня на мысль уничтожать временные объекты в конце предложения, в котором они были сконструированы. Тогда приведенный выше пример был бы не только корректным, но и гарантированно переносимым. Однако при этом перестали бы работать другие, почти эквивалентные примеры: void g(String si, String s2) { const char* p = sl+s2; printf( %s ,p); ... При использовании стратегии уничтожения в конце предложения С-строка, на которую указывает р, оказалась бы во вре.менном объекте, представляющем sl+s2, и была бы уничтожена в конце предложения инициализации р. Дискуссии о продолжительности жизни временных объектов не затихали в комитете примерно два года, пока Дэг Брюк (Dag Вгьск) не положил им конец. Перед этим комитет потратил массу времени в спорах о сравнительных достоинствах тех или иных решений. Все соглашались, что ни одно из них не совершенно. Мое мнение, выраженное довольно резко, заключалось в том, что промедление смерти подобно , поэтому хоть какому-то решению нужно отдать предпочтение. Я думаю, что был выбран наилучший вариант. В июле 1993 г. Дэг Брюк представил отчет о состоянии дел по этому вопросу, основанный главным образом на работе Эндрю Кенига, Скотта Тэрнера и Тома Пеннелло. В нем были определены семь вариантов установления точки разрушения временного объекта: □ сразу после первого использования; □ в конце предложения; □ в следующей точке ветвления; □ в конце блока (оригинальное правило С++, реализованное в Cfront); □ в конце функции; □ после последнего использования (неявно подразумевается сборка мусора); □ где-то между первым использованием и концом блока (правило ARM). Оставляю подбор аргументов в пользу каждого варианта в качестве упражнения для читателя. Однако можно найти и серьезные возражения против каждого из указанных подходов. Следовательно, необходимо выбрать вариант с оптимальным сочетанием преимуществ и недостатков. Мы также рассматривали возможность разрушения временного объекта после его последнего использования в блоке. Но это потребовало бы анализа потока выполнения, а у нас не было уверенности, что любой компилятор сможет провести такой анализ достаточно надежно, чтобы точка за последни.м использованием в блоке означала бы в любой реализации одно и то же. Отмечу, что локального анализа потока недостаточно для предотвращения преждевременного уничтожения , например потому, что конверторы, возвращающие указатель на внутреннюю область объекта, часто определяются не в той единице трансляции, в которой используются. Попытка запретить такие функции не имела смысла, так как при этом перестало бы работать слишком много программ. Да и контроль над ситуацией был невозможен. С 1991 г. комитет склонялся к варианту конца предложения . В обиходе он получил название EOS (end of statement). Оставалось точно определить, что же такое конец предложения . Рассмотрим пример: void h(String sl, String s2) { const char* p; if (p = sl+s2) { ... Будет ли значение p использоваться внутри предложения блока? Иначе говоря, нужно ли уничтожать объект, содержащий sl+s2, в конце условия или в конце всего предложения i f ? Ответ: объект, содержащий s 1+s2, будет уничтожен в конце условия. Было бы абсурдно гарантировать что-то относительно предложения if (р = sl+s2) printf( %s ,р); оставляя р = Sl+s2; printf( %s ,р) ; зависящим от реализации. Как следует обрабатывать ветвления внутри выражения? Например, должно ли гарантированно работать такое предложение: if ((р = sl+s2) && р[0]) { ... Да, должно. Но гораздо проще привести готовый ответ, чем объяснить специальные правила для &&, И и ?:. Против этого предложения выдвигались возражения,
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |