ЖАНРЫ

Справочное руководство по C++

Страустрап Бьярн

Шрифт:

void g

{

 derived d;

 base* bp = &d; // стандартное преобразование: derived* в base*

 bp-›vf1; // вызов derived::vf1

 bp-›vf2; // вызов base::vf2

 bp-›f; // вызов base::f

}

Здесь три вызова для объекта d класса derived приведут к обращениям к derived::vf1, base::vf2 и base::f соответственно. Иными словами, интерпретация вызова виртуальной функции зависит от типа объекта, для которого она вызывается, тогда как интерпретация вызова невиртуальной функции-члена зависит только от типа указателя или ссылки на этот объект. Например, выражение bp-›vf1 приведет к вызову derived::vf1, поскольку bp указывает на объект класса derived, в котором функция derived::vf1 подавляет виртуальную функцию base::vf1.

Наличие спецификации virtual означает, что функция является членом, поэтому виртуальная функция не может быть глобальной функцией (не членом) (§R.7.1.2). Точно так же виртуальная функция не может быть статическим членом, т.к. для вызова виртуальной функции необходимо наличие определенного объекта, который указывает, какую функцию надо вызывать. В другом классе виртуальную функцию можно описать как друга. Функция, подавляющая виртуальную, сама считается виртуальной функцией. Спецификацию virtual можно использовать для подавляющей функции производного класса, но это избыточно. Виртуальная функция может быть определена или описана в базовом классе как чистая (§R.10.3). Виртуальную функцию, которая определена в базовом классе, не нужно определять в производном классе: при всех вызовах будет использоваться функция, определенная в базовом классе.

Механизм виртуальности при вызове отключается, если есть явное уточнение имени с помощью оператора разрешения области видимости (§R.5.1), например:

class B { public: virtual void f; };

class D: public B { public: void f; };

void D::f { /*… */ B::f; }

Здесь обращение к f из D приводит к вызову B::f, а не D::f.

R.10.3 Абстрактные классы

Абстрактные классы дают средство для представления в языке общих понятий, таких, например, как фигура, для которых могут использоваться только конкретные их варианты, например, круг или квадрат. Кроме того абстрактный класс позволяет задать интерфейс, разнообразные реализации которого представляют производные классы.

Абстрактным называется класс, который можно использовать только как базовый для некоторого другого класса, т.е. нельзя создать никакого объекта абстрактного класса кроме того, который представляет базовый класс для некоторого производного класса. Класс считается абстрактным, если в нем есть хотя бы одна чистая виртуальная функция. При описании класса виртуальная функция описывается как чистая с помощью спецификации-чистой (§R.9.2). Чистую виртуальную функцию не нужно определять, если только она явно не вызывается с помощью конструкции уточненное-имя (§R.5.1). Рассмотрим пример:

class point {/*… */};

class shape {// абстрактный класс

 point center;

 //…

public:

 point where { return center; }

 void move(point p) { center=p; draw; }

 virtual void rotate(int) = 0; // чистая виртуальная

 virtual void draw = 0; // чистая виртуальная

 //…

};

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

shape x; // ошибка: объект абстрактного класса

shape* p; // нормально

shape f; // ошибка

void g(shape); // ошибка

shape& h(shape&); // нормально

Чистые виртуальные функции и наследуются как чистые виртуальные функции, например:

class ab_circle: public shape {

 int radius;

public:

 void rotate(int) {}

 ab_circle::draw; // чистая виртуальная функция

};

Поскольку функция shape::draw является чистой виртуальной функцией, то такой же будет по определению и функция ab_circle::draw. Для приведенного ниже описания класс circle не будет абстрактным, и у функции circle::draw где-то должно существовать определение.

class circle: public shape {

 int radius;

public:

 void rotate(int) {}

 void draw; // должна быть где-то определена

};

Функции-члены можно вызывать из конструктора абстрактного класса, результат прямого или косвенного вызова чистой виртуальной функции для объекта, созданного с помощью такого конструктора, неопределен.

R.10.4 Сводка правил области видимости

Теперь можно свести воедино правила областей видимости для программы на C++. Эти правила одинаково применимы для всех имен (включая имя-typedef (§R.7.1.3) и имя-класса (§R.9.1)) и в любом контексте, для которого они допустимы по синтаксису языка. Здесь рассматриваются только области видимости на лексическом уровне, вопросы связывания обсуждаются в §R.3.3. Понятие момента описания было введено в §R.3.2.

Всякое использование имени должно быть однозначным (не считая перегрузки) в области его видимости (§R.10.1.1). Правила доступа (§R.11) начинают действовать только тогда, когда имя можно однозначно найти в области его видимости. Только при условии, что права доступа к имени не нарушены, начинается проверка типа объекта, функции или элемента перечисления.

Имя, которое используется вне любой функции или класса, или перед которым стоит унарная операция разрешения области видимости :: (и которое не уточняется бинарной операцией :: или операциями -› или .), должно быть именем глобального объекта, или функции, или элемента перечисления, или типа.

Имя, задаваемое после X:: или obj., где obj типа X или типа ссылка на X, а также имя, задаваемое после ptr-›, где ptr типа указатель на X, должно быть именем члена класса X или членом базового по отношению к X класса. Помимо этого, в обращении ptr-›имя ptr может быть объектом класса Y, в котором есть функция operator-›, описанная таким образом, что ptr-›operator в конечном счете оказывается указателем на X (§R.13.4.6).

Имя, которое не уточняется одним из описанных выше способов, и, которое используется в функции, не являющейся членом класса, должно быть описано в том блоке, где оно используется, или в объемлющем блоке или должно быть глобальным. Описание локального имени скрывает описания того же имени в объемлющих блоках, а также его описания как глобального имени. В частности, перегрузка имени невозможна для имен в разных областях видимости (§R.13.4).

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

Поделиться с друзьями: