Ответ 1
Здесь моя наименее интрузивная версия без SFINAE. Протестировано с помощью Apple LLVM 5.1 и g++ 4.8.2. Это только одна реализация общей идеи, описанной ниже кода.
#include <cstdio>
// global "system" function to test; generally something like `fopen` in a real test
const char* GetString() { return "GLOBAL"; }
// list all global functions that could possibly be overridden by function pointers
// (initialize them with the original function) and provide template overriding function.
struct PossibleOverridesList
{
decltype(&::GetString) GetString = &::GetString;
template<typename O>
void setOverrides() {
O::setMyOverrides(this);
};
};
// provides no overrides of the standard system functions being tested
// (setOverrides method does nothing)
struct NoOverrides { static void setMyOverrides(PossibleOverridesList* ol){}; };
// set of functions overriding the system functions being tested
// (setOverrides method sets the desired pointers to the static member functions)
struct TestOverrides {
// if this were `fopen` this might be a version that always fails
static const char* GetString() { return "OVERRIDE"; }
static void setMyOverrides(PossibleOverridesList* ol) { ol->GetString = &GetString; };
};
// test case (inheritance doesn't depend on template parameters, so it gets included in the lookup)
struct Test : PossibleOverridesList {
void Run() {
printf("%s\n", GetString());
}
};
int main() {
// test with no overrides; use the system functions
Test test1;
test1.setOverrides<NoOverrides>();
test1.Run();
// test with overrides; use test case version of system functions
Test test2;
test2.setOverrides<TestOverrides>();
test2.Run();
}
Моя идея такова: поскольку я понимаю, что вы не хотите изменять код внутри Run()
, нет способа поиска немодифицированного имени из класса шаблона. Поэтому некоторый (вызываемый) объект-заполнитель должен быть в области видимости, когда GetString
просматривается, внутри которого мы все еще можем решить, какую функцию вызывать (если GetString()
внутри Run()
связывается с глобальным GetString()
один раз, мы не можем сделать что-нибудь позже, чтобы изменить его). Это может быть функция в окружающем пространстве имен или указатель объекта/функции функции/функции в базовом классе (не шаблон). Я выбрал последнего здесь (с классом PossibleOverridesList
), чтобы как можно ближе приблизиться к исходному коду. Этот заполнитель по умолчанию вызывает исходную версию функции, но может быть изменен классами, аналогичными классам переопределения. Единственное отличие состоит в том, что для этих классов переопределения нужен дополнительный метод setMyOverrides(PossibleOverridesList*)
, который соответственно устанавливает указатели на функции в классе-заполнителе.
Изменения в главном не являются строго необходимыми. В моем коде вы должны вызвать setOverrides<...OverriderClass...>()
вместо указания OverriderClass в объявлении. Но должно быть легко написать класс шаблона-оболочки, который наследует от Test и сохраняет исходный синтаксис, внутренне вызывая метод setOverrides
с его аргументом шаблона сам по себе во время построения.
Приведенный выше код имеет несколько преимуществ перед решением предоставления оберток в классе NoOverrides
(ваше предложение - ваш вопрос):
- С обертками, если у вас много переопределенных классов со многими переопределенными функциями, вам придется всегда предоставлять оболочку для всех функций, которые вы не хотите переопределять. Здесь есть только один глобальный список функций, которые могут быть переопределены,
PossibleOverridesList
. Это легко заметить, если вы забыли его, потому что попытка установить этот элемент в методеsetMyOverrides
соответствующего класса переопределения не будет компилироваться. Таким образом, есть только два места, чтобы что-то изменить, если вы добавите другой переопределенный метод в любой класс переопределения. - Вам не нужно воспроизводить подпись функции для обертки системной функции
- Вам не нужно изменять код внутри
Run()
, чтобы использовать функции из унаследованного класса. - Я не знаю, может ли в вашем исходном сложном коде VС++ Test действительно наследовать от нескольких классов переопределения. Но если это возможно, пакетное решение не будет работать, так как вы не знаете, как квалифицировать метод (вы не знаете, из какого унаследованного класса он приходит). Здесь вы можете легко вызвать метод
setOverrides()
несколько раз в тестовом объекте для последовательного замены определенных функций.
Конечно, есть также ограничения, например, если исходная системная функция не находится в пространстве имен ::
, но, возможно, они не более строгие, чем для решения оболочки. Как я уже сказал выше, есть, вероятно, очень разные реализации, которые используют одну и ту же концепцию, но я думаю, что наличие какой-либо формы по умолчанию для переопределенных методов неизбежно (хотя мне бы хотелось увидеть решение без него).
EDIT:
Я просто придумал еще менее навязчивую версию, которая нуждается только в дополнительном классе PossibleOverridesList
, но без изменений в Run()
, main()
. Самое главное, нет необходимости в методах setOverrides
, а шаблонное наследование Test<Override>
с исходного кода сохраняется!
Хитрость здесь заключается в использовании виртуальных методов вместо статических и использования виртуального наследования. Таким образом, вызов функции GetString()
в Run()
может привязываться к виртуальной функции в PossibleOverridesList
(поскольку это наследование не зависит от параметра шаблона). Затем во время выполнения вызов отправляется в самый производный класс, то есть в класс переопределения. Это однозначно из-за виртуального наследования, поэтому на самом деле в классе присутствует только один объект PossibleOverridesList
.
Итак, в итоге, минимальные изменения в этой версии:
- define
PossibleOverridesList
со всеми функциями, которые могут быть переопределены (как функции виртуальных членов), перенаправлением на исходную системную функцию. - изменить методы-члены в классах переопределения от
static
доvirtual
. - пусть все переопределить, а классы
Test
наследуют фактически от OptionOverridesList в дополнение к любому другому наследованию. ДляNoOverride
это не является строго необходимым, но приятным для согласованного шаблона.
Здесь код (опять же, проверенный с Apple LLVM 5.1 и g++ 4.8.2):
#include <cstdio>
// global "system" function to test; generally something like `fopen` in a real test
const char* GetString() { return "GLOBAL"; }
// list all global functions that could possibly be overridden by function pointers
struct PossibleOverridesList
{
virtual const char* GetString() {return ::GetString();};
};
// provides no overrides of the standard system functions being tested
struct NoOverrides : virtual PossibleOverridesList { };
// set of functions overriding the system functions being tested
struct TestOverrides : virtual PossibleOverridesList {
// if this were `fopen` this might be a version that always fails
virtual const char* GetString() { return "OVERRIDE"; }
};
// test case (inheritance from first class doesn't depend on template parameter, so it gets included in the lookup)
template <typename Override>
struct Test : virtual PossibleOverridesList, Override {
void Run() {
printf("%s\n", GetString());
}
};
int main() {
// test with no overrides; use the system functions
Test<NoOverrides> test1;
test1.Run();
// test with overrides; use test case version of system functions
Test<TestOverrides> test2;
test2.Run();
}