допустим, есть базовый класс: Код (Text): class Base { public: static Base* Create(); virtual void Release(); virtual uint32_t Type() { return TYPE_BASE; } }; он олицетворяет пустой объект... у него есть N-наследников, которые, перегружая интерфейсные функции базового класса, олицетворяют каждый свой объект... все объекты сами себя создают с помощью статической функции Create и сами себя удаляют с помощью виртуальной функции Release, конструкторы, деструкторы, операторы new и delete закрыты в базовом классе: Код (Text): class ChildA : public Base { public: static Base* Create(); virtual void Release(); virtual uint32_t Type() { return TYPE_CHILD_A; } }; class ChildB : public Base { public: static Base* Create(); virtual void Release(); virtual uint32_t Type() { return TYPE_CHILD_A; } }; то есть указатель на базовый класс представляет собой гетерогенный контейнер для создаваемых наследников ChildA - ChildX, тип объекта в таком гетерогенном контейнере возможно получить воспользовавшись виртуальной функцией Type... вопрос в том, возможно ли как то (так как идеалогически класс Base олицетворяет пустой класс), асоциировать с нулевым указателем (Base* pObj = NULL виртуальные функции базового класса? то есть допустим, если вызывается функция Type для нулевого указателя на Base, то использовалась бы функция из состава класса Base... понятно, что указатель на таблицу виртуальных функций лежит внутри класса, и при простом таком вызове будет ошибка обращения по нулевым указателям... как такую задачу лучше всего решить? думал о том, чтобы сделать обертку, унаследованную от Base и содержащую внутри себя указатель на потомка Base, и уже в перегруженных функциях делать проверку указателя внутри на NULL и возвращать соответствующие значения, но это решение мне как то не нравится, кажется, что есть более красивое решение... что подскажите?
Не понятно зачем это нужно. Но может подойдёт паттерн шаблонный метод? Невиртуальная функция вызывает виртуальную и соответственно проверяет на NULL this. Но для чего всё это?
Rel Дружеский совет: 1) static Base* Create(); Данный метод присутствует и в базовом и в производных классах и при том он обычный, не виртуальный. Майерс, Effective C++, правило 36 - Никогда не переопределяйте наследуемые невиртуальные функции. Функции типа Create называют фабричными или виртуальными конструкторами - в зависимости от передаваемых параметров они способны создавать объекты различных типов. Оставьте её только в базовом классе. 2) Код (Text): class ChildB : public Base { public: static Base* Create(); virtual void Release(); virtual uint32_t Type() { return TYPE_CHILD_A; } }; Принято писать ключевое слово virtual для виртуальных функций только в базовом классе. В производных virtual не пишут никогда, чтобы не вводить в заблуждение программистов. 3) Нужен ли метод Release, возможно что будет достаточно применения виртуального деструктора. Хотя я точно не знаю с какой целью вы используетет Release.
Nafanya По всем этим пунктам нет той однозначности о которой Вы пишите. Ну или хотя-бы аргументируйте этот копипаст мыслей. Потому-что это написал Майерс? Не всегда нужно работать с классом через базовый тип, отсюда виртуальность при переопределении не всегда нужна. Базовый тип должен знать о производных? Заблуждение в чём? Что функция виртуальна только начиная с данного класса? Это маловероятно. Наоборот удобно видеть виртуальность не залезая в дебри базового типа. Всё это не более чем догмы, для начинающих.
Booster Программисты обычно различают наследование интерфейса и наследование реализации, если подробней - то есть 3 варианта: 1) Наследование интерфейса virtual void func() = 0; 2) Наследование интерфейса и реализации по умолчанию virtual void func(); 3) Наследование интерфейса и обязательной реализации void func(); В зависимости от поставленной цели метод в базовом классе делается либо виртуальным, либо чисто виртуальным, либо не виртуальным. Если Вы объявили метод в базовом не виртуальным - значит вы наследуете в производный класс обязательную реализацию, которая не должна переопределяться, чтобы обеспечить одинаковое поведение. В то же время Вы вдруг пытаетесь переопределить её обязательную реализацию в производном - что недопустимо, т.к. нарушается логика. Если операция выполняется по-разному для объекта базового и для объекта производного класса(необходимо обеспечить различное поведение), то необходимо наследовать интерфейс и реализацию по умолчанию, что достигается за счёт использования виртуальных функций.
Скотт Мейерс пишет не для начинающих. На обложке книги Effective STL- Уровень: опытный/эксперт, Серия: библиотека программиста. Единственное - жаль, что в Effective C++, More Effective C++ о практическом применении шаблонов дана довольно скудная информация.
Nafanya Кто это сказал? Программист волен поступать по своему усмотрению. Вы снова пишите заученные постулаты. Семантика может быть различной. virtual не определяет логику использования или намерений, это лишь особенность вызова. Если объект приводится к базовому типу, то почему он не может вести себя как базовый? Написать можно что угодно, бумага всё стерпит. Постулаты там общеизвестные, но только эксперт умеет мыслить сам, а не заученными истинами.
Booster Лично мне эти постулаты очень помогли, после прочтения данных книг я разложил для себя всё по полочкам, нет больше каши, и неразберихи - благодаря правилам Майерса создаются точные, строгие, как в математике рамки, за которые нельзя выходить, уже точно представляешь - как можно писать код, а как нельзя.
Вообще, переопределение статических функций может использоваться в CRTP для статического полиморфизма
Rel Если я правильно все понял то тебе может помочь создание класса BaseNull который будет унаследован от Base, он должен быть статичным, далее сделай контроль за указателями (какие-нибудь обертки аля hand made SmartPtr), тогда ты можешь вместо NULL возвращать указатель на этот класс и ошибок обращения по нулевому указателю не будет, предположу что можно перегрузить операторы сравнения, которые позволят сравнить умный указатель, который ссылается на BaseNull, с nullptr и вернуть TRUE. ИМХО: Вообще бы здесь использовать абстрактную фабрику, а если процессс создания сложный то добавить фасад + строитель. В этой фабрике уже регистрировать строителей классов. Тогда будет централизованный узел отвечающий за создание классов, не будет такого хардкода и фрагментированной статики разбросанной по проекту. Возможно в контексте задачи оно и не надо.
спасибо всем за ответы... решил создать контейнер в стиле Александрескувских интеллектуальных указателей для объектов... ЗЫ кстати посоветуйте какую-нить книжку интересную почитать в стиле Современное проектироание на С++ или может что-то по C++0x...