Насколько пуристом ты хочешь быть? :)
Правильный ответ на этот вопрос связан с поддержанием инвариантов. Правильный способ сделать это довольно сложно.
В вашем базовом классе вы определяете публичные методы для обеспечения целого доступа к классу. Все эти методы должны быть конкретными. Ключевым моментом здесь является то, что публичные инварианты должны храниться до и после вызова этих функций. Эти функции никогда не должны вызывать друг друга, они вызывают только защищенные и закрытые методы. Эти функции должны быть аксиоматическими: они должны быть достаточно минимальным набором, необходимым для захвата желаемой семантики.
Большинство вычислений, которые могут быть выполнены с использованием этих методов, должны быть глобальными или, по крайней мере, открытыми статическими элементами.
Вы также предоставляете чисто виртуальные методы, которые являются хуками для реализации деталей в зависимости от представления в производных классах. Виртуальные функции в базе должны быть приватными. Обычный совет здесь (публичный) совершенно неверен. Виртуальные функции - это детали реализации. Единственное исключение: виртуальный деструктор должен быть публичным.
Частные вспомогательные функции также можно поместить в базу.
Также может быть полезно иметь защищенные методы в базе: они будут вызывать частных помощников или виртуальных. Как и выше: защищенные методы никогда не должны вызывать защищенные или открытые методы. Защищенные функции поддерживают более слабый инвариант, чем общедоступный, до и после каждого вызова.
Частные функции обычно поддерживают очень слабые инварианты.
Причиной такой строгой иерархии является обеспечение правильного ведения инвариантов объектов.
Теперь в производном классе вы предоставляете реализацию. В идеале виртуальные функции здесь были бы невидимыми , более сильное условие, чем просто частные. Эти виртуальные функции вообще не должны вызываться в производном классе. только разрешенный доступ осуществляется через защищенные функции базы.
Все методы производных классов, включая деструктор, должны быть закрытыми. Конструкторы должны быть открытыми, но они не являются методами класса.
Чтобы полностью понять эти правила, вы должны тщательно продумать инварианты. Общедоступные инварианты могут считаться содержащими до вызова публичного метода, а должны содержать после его завершения. Следовательно, вы не можете вызывать такие функции внутри класса или какого-либо класса, производного от него, потому что эти функции используются для изменения представления между началом и концом публичной функции и, таким образом, неизбежно нарушают публичные инварианты: поэтому они должны не вызывать публичные функции.
Тот же аргумент применяется к защищенным функциям: функции могут вызывать функции только с более слабыми инвариантами .
Виртуальные функции всегда вызываются публикой из общедоступных оболочек базового класса, чтобы гарантировать, что последовательность операций, которая нарушает публичные инварианты, никогда не прерывается при возврате публике со сломанным инвариантом. Множество виртуалов сами представляют инвариантную структуру, которую должно иметь любое представление. При этом все манипуляции с представлением для выполнения вычислений для публичных клиентов могут быть абстрагированы в базу.
На практике эти правила обычно не соблюдаются, поскольку они часто генерируют тривиальные обертки, и для написания и поддержки требуется много дополнительного кода. Поэтому виртуальные функции часто оказываются открытыми, даже если это в принципе совершенно и совершенно неверно.