Программирование >>  Полиморфизм без виртуальных функций в с++ 

1 ... 44 45 46 [ 47 ] 48 49 50 ... 144


хранится сумма 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]) { ...

Да, должно. Но гораздо проще привести готовый ответ, чем объяснить специальные правила для &&, И и ?:. Против этого предложения выдвигались возражения,



1 ... 44 45 46 [ 47 ] 48 49 50 ... 144

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика