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

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


int х=7;

int х=х; локальная х

Однако, когда я только проектировал классы и встраиваемые функции. Дуг Макилрой убедительно возразил, что применение данного правила к объявлениям классов может привести к путанице. Например:

int X;

class X {

void f() { int у = x; } ::X или X::X? void g(); int x;

Несмотря на то что i используется для определения типа, само оно именем типа не является, поэтому не подпадает под действие правила переопределения. Правила ANSI/ISO говорят, что этот пример некорректен, так как i переопределено после использования. Вот другой пример:

class Т { А f();

void g() { А а; /* ... */ } typedef int А;

Предположим, что тип А не был определен вне Т. Законно ли объявление Т: : f () ? А определение Т: : g () ? В ARM объявление Т: : f () считается незаконным, поскольку тип А в этой точке не определен. Это не вызывает противоречия с ANSI/ISO. С другой стороны, согласно ARM, определение g () законно, если правило переписывания интерпретировать в том смысле, что переписывание производится до начала синтаксического анализа, и незаконно, если допустить, что сначала выполняется синтаксический анализ, а затем - переписывание. Проблема заключается в том, является ли А именем типа к моменту начала анализа. Я полагаю, что в ARM выражена первая точка зрения (то есть объявление Т: : g () законно), но не стал бы утверждать, что это бесспорно.

6.3.1.2. Зачем допускать опережающие ссылки?

Видимо, всех этих проблем можно было бы избежать, если настоять на применении только однопроходных анализаторов. Имя можно использовать только тогда, когда оно объявлено выше/раньше , а то, что происходит ниже/позже , не влияет на это объявление. В конце концов, именно это правило применяется в С и в C-I-I-. Например:

int х;

void f() {

int у=х; глобальная х



void h() { int у = x; } X::x

void X::g() { int у = x; } X::x

Если объявление X велико, то присутствие различных х часто будет оставаться незамеченным. Более того, если член х используется не очень последовательно, то изменение порядка членов приведет к молчаливому изменению семантики. Вынесение определения функции за пределы объявления класса также может повлечь за собой изменение семантики без предупреждения. Правила переписывания и переопределения запщщали от подобных коварных ошибок и обеспечивали некоторую свободу при реорганизации классов.

Эти аргументы применимы не только к классам, но издержки компилятора на обеспечение такой защиты приемлемы лишь для классов, и лишь для классов можно избежать проблем совместимости с С. Кроме того, именно в объявлениях классов переупорядочение делается чаще всего и как раз там оно может дать нежелательные побочные эффекты.

6.3.1.3. Правила разрешения имен ANSI/ISO

С годами мы нашли много примеров, для которых явных правил ARM было недостаточно. Зависи.мость от порядка в них была совершенно неочевидной и потенциально опасной, а интерпретация правил - неод}Юзначной. Один из интересных примеров нашел Скотт Тэрнер:

typedef int Р();

typedef int Q();

class X {

static P(Q); определим Q как P

эквивалентно static int QO

скобки, в которые взят Q, избыточны

Q в этой области действия уже не тип

static Q(P); определим Q как функцию, принимающую аргумент типа Р и возвращающую int. эквивалентно static int Q(int())

Объявление двух функций с одинаковыми именами в одной и той же области действия допусти.мо, если типы их apiyMcnroB достаточно различаются. При изменении порядка объявления членов мы получим две функции с именем Р. Уда-ли.м typedef для Р или для Q и получим новую семантику.

Заметим, что этот пример, как и многие другие, основан на злосчастном правиле неявного int , унаследованном от С. Enie более десяти лет назад я пытался избавиться от него (см. раздел 2.8.1). Но, увы, есть примеры и другого рода:

int b; class Z {

static int a[si2eof(b)];

static int b[si2eof(a)];



Данный пример содержит ошибку, поскольку b изменяет смысл после использования. К слову сказать, такие ошибки компилятору обнаружить легко, не то что в случае с Р (Q).

В марте 1993 г. в Портленде комитет одобрил следующие правила:

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

□ имя, использованное в классе S, должно относиться к одному и тому же объявлению независи.мо от того, вычисляется оно в своем контексте или в полной области действия S. Полная область действия S состоит из самого класса S, его базовых классов и всех классов, объемлющих S. Часто это правило называют правилом пересмотра (the reconsideration rule);

□ если изменение порядка следования объявлений членов дает новую, но корректную профамму с учетом правил 1 и 2, семантика профаммы не определена. Часто это называют правилом переупорядочения (the reordering rule).

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

6.3.2. Время жизни объектов

Для многих операций в C+-I- приходится со.здавать временные значения. Например:

void f(X al, X а2) {

extern void g(const X&);

X z;

...

z = al+a2;

g(al+a2);

.. .

Обычно для хранения результата al+a2 перед присваиванием его переменной Z необходим объект (вероятно, типа X). Точно так же нужен объект, в который по-.мещается сумма al+a2 перед передачей функции g (). Предположим, что в классе X есть деструктор. В какой момент он должен вызываться для временного объекта? Первоначально ответ на этот вопрос звучал так: в конце блока, точно так же, как для любой другой локальной переменной . Но оказалось, что тут есть две проблемы:

□ иногда при этом объект уничтожается слишком рано. Например, g () может поместить указатель на свой аргу.мент (временный объект, в котором



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

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