Сообщение Re: side virtual method implementation от 13.02.2018 21:52
Изменено 22.04.2019 9:24 deleted2
Re: side virtual method implementation
ИМХО, суть проблемы вот в этом:
CS>class AbstractFoo {
...
CS>#if WINDOWS
CS>class AbstractFooImpl {
CS> int do_something() { return 0; }
CS>}
CS>#elif MACOS
CS> ...
CS>#endif
CS>но он не работает, т.к. AbstractFoo::do_something() и AbstractFooImpl::do_something() есть разные сущности.
Да, конечно, в разных системах всякое сделано по-разному. Когда начинаешь писать обобщенно, рано или поздно с этой разницей сталкиваешься и вся ранее казавшаяся стройной структура классов рушится на глазах. Приходится или делать костыли, чтобы в другой системе работало, или все приходится переписывать заново, понимая внезапно появившееся ограничение. Я такое проходил несколько раз и постоянно натыкался на это.
CS>Каким образом это сделать более кошерно ?
Прежде чем добавлять универсальный абстрактный класс, зависимый от платформенных фичь, лучше много раз подумать, прежде чем так делать.
Предлагаю сместить абстракцию ближе к клиентскому коду.
Например, что-то типа:
или, если полагаться на возможность компилятора выкинуть лишний if(false):
Чуть чище выходит, но ИМХО, пусть будут макросы.
Лучше без наследования, поскольку UniversalFeatures должен разруливать разницу между платформами. WindowsFeatures и MacOSFeatures не обязательно делать максимально похожими. Их задача — максимально хорошо делать задачу в своей платформе. А задача UniversalFeatures разруливать разницу.
А уже от UniversalFeatures наследуйте свои платформенно-независимые вещи.
Другой вариант, если не хочется писать #ifdef, можно обернуть все в шаблоны. Тогда C++ будет сам выкидывать ненужное, и не нужны будут даже виртуальные функции:
или даже:
В этом случае, благодаря шаблонам можно из платформенного кода вызывать функции клиента без виртуальных прослоек:
Не обязательно шаблонные кишки отдавать клиенту. Можно Client сделать абстрактной базой последующей иерархии, а шаблонные кишки будут скрыты в имплементации. Дальше уже будет классическое ООП.
Я в своих решениях предпочитаю вариант с шаблонами. Как раз из-за того что не нужны виртуальные функции и есть возможность из платформенно-зависимого кода очень легко и просто вызывать в наследниках.
CS>Пока два варианта видится : virtual inheritance и #define простихоспидя. Что еще можно придумать?
Только не virtual. Это не решит задачу разницу между платформами.
PS. Множественное наследование — зло. Лучше юзеру его не давать, внутри себя можно, но в классах, какие никогда не будут видны пользователю.
CS>class AbstractFoo {
...
CS>#if WINDOWS
CS>class AbstractFooImpl {
CS> int do_something() { return 0; }
CS>}
CS>#elif MACOS
CS> ...
CS>#endif
CS>но он не работает, т.к. AbstractFoo::do_something() и AbstractFooImpl::do_something() есть разные сущности.
Да, конечно, в разных системах всякое сделано по-разному. Когда начинаешь писать обобщенно, рано или поздно с этой разницей сталкиваешься и вся ранее казавшаяся стройной структура классов рушится на глазах. Приходится или делать костыли, чтобы в другой системе работало, или все приходится переписывать заново, понимая внезапно появившееся ограничение. Я такое проходил несколько раз и постоянно натыкался на это.
CS>Каким образом это сделать более кошерно ?
Прежде чем добавлять универсальный абстрактный класс, зависимый от платформенных фичь, лучше много раз подумать, прежде чем так делать.
Предлагаю сместить абстракцию ближе к клиентскому коду.
Например, что-то типа:
#ifdef _WIN32
class WindowsFeatures { };
#else
class MacOSFeatures { };
#endif
class UniversalFeatures {
#ifdef _WIN32
WindowsFeatures
#else
MacOSFeatures
#endif
features;
...
void someCode() {
#ifdef MACOS
...
#endif
}
};
или, если полагаться на возможность компилятора выкинуть лишний if(false):
void someCode() {
if (features.isMacOS) {}
}
Чуть чище выходит, но ИМХО, пусть будут макросы.
Лучше без наследования, поскольку UniversalFeatures должен разруливать разницу между платформами. WindowsFeatures и MacOSFeatures не обязательно делать максимально похожими. Их задача — максимально хорошо делать задачу в своей платформе. А задача UniversalFeatures разруливать разницу.
А уже от UniversalFeatures наследуйте свои платформенно-независимые вещи.
Другой вариант, если не хочется писать #ifdef, можно обернуть все в шаблоны. Тогда C++ будет сам выкидывать ненужное, и не нужны будут даже виртуальные функции:
template <class Platform>
class UniversalFeatures {
Platform features;
};
или даже:
#ifdef _WIN32
template <class Client>
class WindowsFeatures {
};
#else
template <class Client>
class MacOSFeatures {
};
#endif
В этом случае, благодаря шаблонам можно из платформенного кода вызывать функции клиента без виртуальных прослоек:
void WindowsFeatures::someFunc() {
thisFunctionIsCallableFromPlatformFeatures();
}
class Client: public MacOSFeatures<Client> {
void thisFunctionIsCallableFromPlatformFeatures() {}
}
Не обязательно шаблонные кишки отдавать клиенту. Можно Client сделать абстрактной базой последующей иерархии, а шаблонные кишки будут скрыты в имплементации. Дальше уже будет классическое ООП.
Я в своих решениях предпочитаю вариант с шаблонами. Как раз из-за того что не нужны виртуальные функции и есть возможность из платформенно-зависимого кода очень легко и просто вызывать в наследниках.
CS>Пока два варианта видится : virtual inheritance и #define простихоспидя. Что еще можно придумать?
Только не virtual. Это не решит задачу разницу между платформами.
PS. Множественное наследование — зло. Лучше юзеру его не давать, внутри себя можно, но в классах, какие никогда не будут видны пользователю.
Re: side virtual method implementation
deleted