[проектирование C++] неявный виртуальный вызов в конструкторе

Realist

Есть правило, что нельзя вызывать виртуальную функцию в конструкторах и деструкторах. Или, по крайней мере, нужно полностью отдавать себе отчет в том, что ты делаешь.
Положим, у меня есть базовый класс и неопределенная куча независимых потомков. Все они должны уметь некоторые, специфичные для класса, данные сохранять в файл и выводить на экран.
Вместо совсем простой иерархии
  
class Base
{
public:
virtual void saveDataToFile(string filename) = 0;
virtual void printData = 0;
};

Я бы предпочел нечто
  
class Base
{
public:
void saveDataToFile(string filename);
void printData;
protected:
virtual string getData = 0;
};


Таким образом я один раз пишу код для открытия файла / вывода на экран. Соответствующие функции невиртуальные, реализованы в базовом классе и используют виртуальную функцию получения строки с данными.
Возвращаемся к принципу не звать виртуальную функцию из конструктора. Положим, базовый класс пишет один программист, а потомков — другие, независимые. А файл реализации базового класса — только в бинарнике. Внимание, вопрос, должны ли (и если да, то откуда) авторы производных классов знать, что лучше не вызывать saveDataToFile и printData В конструкторе и деструкторе?
Спасибо

Devid

Внимание, вопрос, должны ли (и если да, то откуда)
Капитан скажет, что должны и из документации, и, наверное, ошибется. Но мне больше интересно зачем нужно было вступление, если весь вопрос в последнем абзаце?

Serab

Капитан скажет, что должны и из документации, и, наверное, ошибется.
а мне что-то кажется, что не ошибется. Это всегда очень заметный момент. Ну вот есть функция, у нее параметры, это документируется, понятно. Но всегда хочется осознавать, от чего еще зависит поведение функции. Ну там «при прочих равных», все дела. Ну вот это важный момент, что она использует функции, дописанные пользователем (тем, кто наследует). А уж то, что пускать подобные функции не стоит в конструкторе, должно быть в крови. Ну либо, обожжешься на этом, запомнишь, на некоторое время. Все равно от ошибок никто не застрахован :)
С другой стороны это что у нас? Шаблонный метод? Конструкция известная, значит должны быть известны и связанные проблемы. Надо вырабатывать эту ассоциацию и приравнивать клиентов шаблонных методов к самим виртуальным функциям.
Или даже проще: что стоит делать в конструкторе? За редким исключением всяких специальных объектов типа смарт-поинтеров или каких-нибудь переключателей внешнего состояния на время работы блока (например, контроллеров критической секции достаточно привести объект в корректное состояние, чтобы он был готов к дальнейшим действиям, в общем, минимум действий в конструкторе — это тоже логичное правило, прежде чем нарушать которое, в общем случае надо подумать.

Serab

getData все еще рекомендуется делать private :)

pitrik2

getData все еще рекомендуется делать private :)
что значит "все еще"?
как это private? разве тогда его потомки смогут переопределить?

Maurog

разве тогда его потомки смогут переопределить?
конечно, метод ведь виртуальный

Serab

как это private? разве тогда его потомки смогут переопределить?
зачем ты у меня это спрашиваешь? Ну вот если ты пишешь сюда, то у тебя, наверное, есть компилятор C++ под рукой. Ну возьми, проверь.
Саттер вообще настаивает на том, что все виртуальные методы должны быть private. С его доводами можно согласиться, но обычно ломает это делать.
А в этом случае автор явно пытается запрятать этот метод getData, ну так почему бы и от наследников его не спрятать?

bansek

ну я бы вообще сказал, что в конструкторе можно вызывать только final (приватные невиртуальные?) методы, которые ты сам определили. все остальное рано или поздно приведет к граблям ...

Serab

Вот, я понял, что мне не нравилось в этом коде. Зачем делать getData закрытым вообще? Все равно любой сможет сохранить объект в файл и прочитать строку оттуда. А что если кто-нибудь хочет сериализовать в строку? Это часто может пригодиться. Итого: просто оставить (пускай даже открытую) виртуальную функцию getData, а сохранять уже другим классом, который может сохранять в файл произвольную строчку :)

Katya19

Секунду. Вроде плохо будет, если вызывать виртуальную функцию в конструкторе Base. А если в конструкторе производных классов, то все нормально сработает. Или нет?

Serab

Ну вдруг там несколько уровней наследования. :)

Katya19

Ну автор треда этого, видимо, не подразумевал.

Serab

Ну хз, хз, автор треда — любитель пытаться предвидеть непредвидимое, это не первый его пост в подобном ключе :)

enochka1145

// Есть правило, что нельзя вызывать виртуальную функцию в конструкторах и деструкторах.
Вызывать можно. Только есть правило, что в момент исполнения конструктора таблица виртуальных функций ещё не работает, поэтому будет вызван совершенно определённый метод (статически связанный этого класса или выше по иерархии.
В деструкторах деструкторы классов наследников уже отработали, сами наследники разрушены, по этой причине связывание опять же статическое.

Realist

Значит, у меня пример дурацкий. Просто я подводил под мысль, что возможна ситуация ошибки, в которой никто не виноват. Такая возможность сильно омрачает мою веру прекрасное.

Realist

Если сделать дерево наследования их трех классов и вызвать printData в конструкторе среднего класса, то будет вызвана getData среднего класса. Интересный и понятный эффект.

Realist

Често говоря, первый раз слышу. Читал, что protected определяет интерфейс для потомков. Абстрактность говорит о необходимости (пере)определить.

Serab

Ну да, интерфейс для потомков. Но почему потомку надо получать строковое представление объекта, а внешнему миру — нет?
Нет, если надо, то без вопросов, просто по умолчанию все же private.

ppplva

Такая возможность сильно омрачает мою веру прекрасное.
Речь все еще о C++?
Оставить комментарий
Имя или ник:
Комментарий: