Форум программистов, компьютерный форум CyberForum.ru
Наши страницы

С++ для начинающих

Войти
Регистрация
Восстановить пароль
 
ITcrusader
Эксперт C++
176 / 162 / 8
Регистрация: 12.02.2013
Сообщений: 410
#1

Mayers S. vs C++11 standard - C++

19.03.2013, 10:20. Просмотров 397. Ответов 4
Метки нет (Все метки)

Привет, ребят!

У Майерса читал: для предоставления виртуальной функции реализации по умолчанию, которой нужно пользоваться по явному требованию наследующего и переопределяющего виртуальную функцию программиста, т.е. чтобы не получилось так, что в наследном классе забыл её переопределить и будет срабатывать реализация из базового класса (а так же с целью незагромождения пространства имен класса функцией defaultImplementation, которую можно было бы использовать для реализации этой идеи, например), используется определение чисто виртуальной функции. Короче, вот выжимка из книги:

Кликните здесь для просмотра всего текста
Оказывается, иногда может быть опасно использовать обычные виртуальные функции, которые обеспечивают как интерфейс функции, так и ее реализацию по умолчанию. Для того чтобы понять, почему имеется такая вероятность, рассмотрим иерархию самолетов в компании XYZ Airlines. XYZ располагает самолетами только двух типов: модель A и модель B, и оба летают одинаково. В связи с этим разработчики XYZ проектирует такую иерархию:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
 class Airport {}; // представляет аэропорты
    class Airplane {
    public:
    virtual void fly(const Airport& destination);
    ...
    };
    void Airplane::fly(const Airport& destination)
    {
    код по умолчанию, описывающий полет самолета
 
    в заданный пункт назначения – destination
    }
    class ModelA: public Airplane {...};
    class ModelB: public Airplane {...};
Чтобы выразить тот факт, что все самолеты должны поддерживать функцию fly, и для того чтобы засвидетельствовать, что для разных моделей, в принципе, могут потребоваться различные реализации fly, функция Airplane::fly объявлена виртуальной. При этом во избежание написания идентичного кода в классах ModelA и ModelB в качестве стандартного поведения используется тело функции Airplane::fly, которую наследуют как ModelA, так и ModelB.
Это классический пример объектно-ориентированного проектирования. Два класса имеют общее свойство (способ реализации fly), поэтому оно реализуется в базовом классе и наследуется обоими подклассами. Благодаря этому проект явным образом выделяет общие свойства, что позволяет избежать дублирования, благоприятствует проведению будущих модернизаций и упрощает долгосрочную эксплуатацию – иными словами, обеспечивает все, за что так ценится объектно-ориентированная технология. Программисты компании XYZ Airlines могут собой гордиться.
А теперь предположим, что дела XYZ идут в гору, и компания решает приобрести новый самолет модели C. Эта модель отличается от моделей A и B, в частности, тем, что летает по-другому.
Программисты компании XYZ добавляют в иерархию класс ModelC, но в спешке забывают переопределить функцию fly:

C++
1
2
3
 class ModelB: public Airplane {
    ... // функция fly не объявлена
    };
В своем коде потом они пишут что-то вроде этого:

C++
1
2
3
4
Airport PDX(...); // PDX – аэропорт возле моего дома
    Airplane *pa = new ModelC;
    ...
    pa->fly(PDX); // вызывается Airplane::fly!
Назревает катастрофа: делается попытка отправить в полет объект ModelC, как если бы он принадлежал одному из классов ModelA или ModelB. Такой образ действия вряд ли может внушить доверие пассажирам.
Проблема здесь заключается не в том, что Airplane::fly ведет себя определенным образом по умолчанию, а в том, что такое наследование допускает неявное применение этой функции для ModelC. К счастью, легко можно предложить подклассам поведение по умолчанию, но не предоставлять его, если они сами об этом не попросят. Трюк состоит в том, чтобы разделить интерфейс виртуальной функции и ее реализацию по умолчанию. Вот один из способов добиться этого:

C++
1
2
3
4
5
6
7
8
9
10
11
 class Airplane {
    public:
    virtual void fly(const Airport& destination) = 0;
    ...
    protected:
    void defaultFly(const Airport& destination);
    };
    void Airplane::defaultFly(const Airport& destination)
    {
    код по умолчанию, описывающий полет самолета в заданный пункт назначения
    }
Обратите внимание, что функция Airplane::fly преобразовна в чисто виртуальную. Она предоставляет интерфейс для полета. В классе Airplane присутствует и реализация по умолчанию, но теперь она представлена в форме независимой функции defaultFly. Классы, подобные ModelA и ModelB, которые хотят использовать поведение по умолчанию, просто выполняют встроенный вызов defaultFly внутри fly (см. также правило 30 о взаимодействии встраивания и виртуальных функций):

C++
1
2
3
4
5
6
7
8
9
10
11
12
class ModelA: public Airplane {
    public:
    virtual void fly(const Airport& destination)
    { defaultFly(destination};}
    ...
    };
    class ModelB: public Airplane {
    public:
    virtual void fly(const Airport& destination)
    { defaultFly(destination};}
    ...
    };
Теперь для класса ModelC возможность случайно унаследовать некорректную реализацию fly исключена, поскольку чисто виртуальная функция в Airplane вынуждает ModelC создавать свою собственную версию fly.

C++
1
2
3
4
5
6
7
8
9
class ModelC: public Airplane {
    public:
    virtual void fly(const Airport& destination)
    ...
    };
    void ModelC::fly(const Airport& destination)
    {
    код, описывающий полет самолета ModelC в заданный пункт назначения
    }
Эта схема не обеспечивает "защиту от дурака" (программисты все же могут создать себе проблемы копированием/вставкой), но она более надежна, чем исходная. Что же касается функции Airplane::defaultFly, то она объявлена защищенной, поскольку действительно является деталью реализации класса Airplane и производных от него. Пассажиры теперь должны беспокоиться только о том, чтобы улететь, а не о том, как происходит полет.
Важно также то, что Airplane::defaultFly объявлена как невиртуальная функция. Это связано с тем, что никакой подкласс не должен ее переопределять – обстоятельство, которому посвящено правило 36. Если бы defaultFly была виртуальной, перед вами снова встала бы та же самая проблема: что, если некоторые подклассы забудут переопределить defaultFly должным образом?
Иногда высказываются возражения против идеи разделения функций на обеспечивающие интерфейс и реализацию по умолчанию, такие, например, как fly и defaultFly. Прежде всего, отмечают противники этой идеи, это засоряет пространство имен класса близкими названиями функций. Все же они соглашаются с тем, что интерфейс и реализация по умолчанию должны быть разделены. Как разрешить кажущееся противоречие? Для этого используется тот факт, что производные классы должны переопределять чисто виртуальные функции и при необходимости предоставлять свои собственные реализации. Вот как можно было бы использовать возможность определения чисто виртуальных функций в иерархии Airplane:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Airplane {
    public:
    virtual void fly(const Airport& destination) = 0;
    ...
    };
    void Airplane::fly(const Airport& destination) // реализация чисто
    { // виртуальной функции
    код по умолчанию, описывающий полет
    самолета в заданный пункт назначения
    }
    class ModelA: pubic Airplane {
    public:
    virtual void fly(const Airport& destination)
    { Airplane::fly(destination);}
    ...
    };
    class ModelB: pubic Airplane {
    public:
    virtual void fly(const Airport& destination)
    { Airplane::fly(destination);}
    ...
    };
    class ModelC: pubic Airplane {
    public:
    virtual void fly(const Airport& destination);
    ...
    };
    void ModelC::fly(const Airport& destination)
    {
    код, описывающий полет самолета ModelC в заданный пункт назначения
    }
Это практически такой же подход, как и прежде, за исключением того, что тело чисто виртуальной функции Airplane::fly заменяет собой независимую функцию Airplane::defaultFly. По существу, fly разбита на две основные составляющие. Объявление задает интерфейс (который должен быть использован в производных классах), а определение задает поведение по умолчанию (которое может использоваться производным классом, но только по явному требованию). Однако, производя слияние fly и defaultFly, мы теряем возможность задать для этих функций разные уровни доступа: код, который должен быть защищенным (функция defaultFly), становится открытым (потому что теперь он находится внутри fly).


Сегодня нахожу кусочек стандарта:
[ Note: a function declaration cannot provide both a pure-specifier and a definition —end
note ] [ Example:
C++
1
2
3
struct C {
virtual void f() = 0 { }; // ill-formed
};
—end example ]

Майерс пишет примеры, которые соответствуют стандартному переносимому C++, в иных случаях - оговаривает обратное.

Я в недоумении.
0
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
19.03.2013, 10:20
Здравствуйте! Я подобрал для вас темы с ответами на вопрос Mayers S. vs C++11 standard (C++):

Non-standard syntax; use '&' to create a pointer to member - C++
for (size_t i = 1; i <= 32; i *= 2) { char* dimensional = to_string(MATRIX * i).c_str; Out::Print( dimensional, 5); ...

Где хранятся правила перехода STANDARD/DST time? - C++
Вроде бы начиная с 1970 года эти locale-dependent правила хранятся имеено в файле локали, но в какой секции? Насколько я знаю, ...

Error C2338: The C++ Standard doesn't provide a hash for this type - C++
Хеш таблицы в руках впервые, необходима неупорядоченность. #include <iostream> #include <conio.h> #include <string> #include...

This file requires compiler and library support for the upcoming ISO C++ standard, C++0x - C++
Не подскажите что за ошибка: #error This file requires compiler and library support for the upcoming \ ISO C++ standard, C++0x. This...

Исправить ошибку "non-standard syntax; use '&' to create a pointer to member" - C++
Доброго времени суток,такая проблема,код вроде написан нормально,но выдает ошибку : "non-standard syntax; use '&' to create a pointer to...

Ошибка компиляции "This file requires compiler and library support for the ISO C++ 2011 standard" - C++
Здравствуйте! У меня проблема с компиляцией, выдает ошибку " #error This file requires compiler and library support for the ISO C++ 2011...

Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Toshkarik
1141 / 858 / 51
Регистрация: 03.08.2011
Сообщений: 2,384
Завершенные тесты: 1
19.03.2013, 10:33 #2
ITcrusader, этого нельзя делать в определении класса. А так можно:
C++
1
2
3
4
5
6
7
struct C {
virtual void f() = 0;
};
 
void C::f() {
   //some code
}
Хоть в наследниках и придется определять чисто виртуальную функцию, они могут вызвать в каких то частях явно функцию, определенную в родителе.

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct C {
virtual void f() = 0;
};
 
void C::f() {
   std::cout << "Base pure func";
}
 
struct B : C {
   virtual void f() {
      C::f();
      std::cout << " from derived" << std::endl;
   }
};
 
int main() {
   C *a = new B();
   
   a->f();
 
   delete a;
}
1
ITcrusader
Эксперт C++
176 / 162 / 8
Регистрация: 12.02.2013
Сообщений: 410
19.03.2013, 10:43  [ТС] #3
Toshkarik, да, сама цель этого приема мне ясна. Просто я полагал, что определение, данное в самом классе отличается от внешнего единственно тем, что функция рекомендуется мной компилятору как inline (только запись неявная). Я полагал, что это единственное отличие (если не брать в расчет хороший тон с разделением кода).

Правильно ли я сейчас понял, что это, по-видимому, не единственная разница? Т.е. в этом случае разделение объявления и определения уже не идет вразрез со стандартом? Если да - не идет, то я только вот не понимаю, а в чем отличие?

P.S. Сама идея и цель использования реализации чисто виртуальной функции мне абсолютно ясна - идея красивая, лучше использования дополнительной, конечно.
0
Toshkarik
1141 / 858 / 51
Регистрация: 03.08.2011
Сообщений: 2,384
Завершенные тесты: 1
19.03.2013, 10:53 #4
Цитата Сообщение от ITcrusader Посмотреть сообщение
Т.е. в этом случае разделение объявления и определения уже не идет вразрез со стандартом?
Все вполне по стандарту.
http://stackoverflow.com/questions/4...definition-why
1
ITcrusader
Эксперт C++
176 / 162 / 8
Регистрация: 12.02.2013
Сообщений: 410
19.03.2013, 14:09  [ТС] #5
Toshkarik, отличная ссылка. Спасибо. Вопрос снят
0
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
19.03.2013, 14:09
Привет! Вот еще темы с ответами:

Иконки компонентов Standard - Delphi
День добрый! Пишу обучалку по пане компонентов Standard. Решил оформить теорию по компонентам в виде BitButtton-ов с иконками самих...

отличия Enterprise от Standard - Oracle
Подскажите, plz, в чём основные отличия этих версий и насколько они существенны...

R2 64-bit Standard и RAID 5 8 Tb - Windows Server
Привет! Проблема в следующем. Есть указанная ось как DC и файл-сервер, развернут на RAID 5 массиве. После установки оси создалось 2...

Использование компонентов страницы Standard - Delphi
На форме располагаются компоненты: на середине формы метка Label с указанием фа-милии студента; ListBox с опциями-цифрами 1, 2, 3, 4; ...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
Yandex
Объявления
19.03.2013, 14:09
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru