Я думаю, что у меня есть ответ, который вы ищете, или, по крайней мере, я почти знаю. Он использует стиль отправки, который вы предложили, был глупым, но я думаю, что он соответствует первым двум критериям, которые вы изложили, и более или менее соответствует третьему.
- Класс упаковки вообще не нужно изменять.
- Он не изменяет обернутый класс вообще.
- Изменяет только синтаксис, вводя функцию диспетчеризации.
Основная идея состоит в том, чтобы создать шаблонный класс, параметром которого является класс объекта, который нужно обернуть, с помощью шаблона dispatch
метода, параметры которого являются аргументом и возвращаемыми типами функции-члена. Метод dispatch просматривает переданный указатель на функцию-член, чтобы увидеть, был ли он вызван ранее. Если это так, он извлекает запись аргументов предыдущего метода и вычисленных результатов, чтобы вернуть ранее вычисленное значение для аргумента, переданного диспетчеру, или вычислить его, если оно новое.
Поскольку то, что делает этот класс-обертка, также называется памятка , я решил назвать шаблон Memo
, потому что он короче, чем CacheWrapper
, и я начинаю предпочитать более короткие имена в старости.
#include <algorithm>
#include <map>
#include <utility>
#include <vector>
// An anonymous namespace to hold a search predicate definition. Users of
// Memo don't need to know this implementation detail, so I keep it
// anonymous. I use a predicate to search a vector of pairs instead of a
// simple map because a map requires that operator< be defined for its key
// type, and operator< isn't defined for member function pointers, but
// operator== is.
namespace {
template <typename Type1, typename Type2>
class FirstEq {
FirstType value;
public:
typedef std::pair<Type1, Type2> ArgType;
FirstEq(Type1 t) : value(t) {}
bool operator()(const ArgType& rhs) const {
return value == rhs.first;
}
};
};
template <typename T>
class Memo {
// Typedef for a member function of T. The C++ standard allows casting a
// member function of a class with one signature to a type of another
// member function of the class with a possibly different signature. You
// aren't guaranteed to be able to call the member function after
// casting, but you can use the pointer for comparisons, which is all we
// need to do.
typedef void (T::*TMemFun)(void);
typedef std::vector< std::pair<TMemFun, void*> > FuncRecords;
T memoized;
FuncRecords funcCalls;
public:
Memo(T t) : memoized(t) {}
template <typename ReturnType, typename ArgType>
ReturnType dispatch(ReturnType (T::* memFun)(ArgType), ArgType arg) {
typedef std::map<ArgType, ReturnType> Record;
// Look up memFun in the record of previously invoked member
// functions. If this is the first invocation, create a new record.
typename FuncRecords::iterator recIter =
find_if(funcCalls.begin(),
funcCalls.end(),
FirstEq<TMemFun, void*>(
reinterpret_cast<TMemFun>(memFun)));
if (recIter == funcCalls.end()) {
funcCalls.push_back(
std::make_pair(reinterpret_cast<TMemFun>(memFun),
static_cast<void*>(new Record)));
recIter = --funcCalls.end();
}
// Get the record of previous arguments and return values.
// Find the previously calculated value, or calculate it if
// necessary.
Record* rec = static_cast<Record*>(
recIter->second);
typename Record::iterator callIter = rec->lower_bound(arg);
if (callIter == rec->end() || callIter->first != arg) {
callIter = rec->insert(callIter,
std::make_pair(arg,
(memoized.*memFun)(arg)));
}
return callIter->second;
}
};
Вот простой тест, показывающий его использование:
#include <iostream>
#include <sstream>
#include "Memo.h"
using namespace std;
struct C {
int three(int x) {
cout << "Called three(" << x << ")" << endl;
return 3;
}
double square(float x) {
cout << "Called square(" << x << ")" << endl;
return x * x;
}
};
int main(void) {
C c;
Memo<C> m(c);
cout << m.dispatch(&C::three, 1) << endl;
cout << m.dispatch(&C::three, 2) << endl;
cout << m.dispatch(&C::three, 1) << endl;
cout << m.dispatch(&C::three, 2) << endl;
cout << m.dispatch(&C::square, 2.3f) << endl;
cout << m.dispatch(&C::square, 2.3f) << endl;
return 0;
}
, который выдает следующий вывод в моей системе (MacOS 10.4.11 с использованием g ++ 4.0.1):
Called three(1)
3
Called three(2)
3
3
3
Called square(2.3)
5.29
5.29
ПРИМЕЧАНИЯ
- Это работает только для методов, которые принимают 1 аргумент и возвращают результат. Это не работает для методов, которые принимают 0 аргументов, или 2, или 3, или больше аргументов. Это не должно быть большой проблемой, хотя. Вы можете реализовать перегруженные версии диспетчеризации, которые принимают разное количество аргументов вплоть до некоторого разумного максимума. Это то, что делает Boost Tuple библиотека . Они реализуют кортежи из 10 элементов и предполагают, что большинству программистов не требуется больше этого.
- Возможность реализации нескольких перегрузок для диспетчеризации - вот почему я использовал шаблон предиката FirstEq с алгоритмом find_if вместо простого поиска в цикле. Это немного больше кода для однократного использования, но если вы собираетесь выполнять подобный поиск несколько раз, то в итоге будет меньше кода в целом и меньше шансов сделать один из циклов слегка ошибочным.
- Это не работает для методов, которые ничего не возвращают, т.е.
void
, но если метод ничего не возвращает, вам не нужно кэшировать результат!
- Это не работает для функций-членов шаблона обернутого класса, потому что вам нужно передать фактический указатель на функцию-член в dispatch, а неинстанцированная функция шаблона не имеет указателя (пока). Может быть, есть способ обойти это, но я еще не очень старался.
- Я еще не проводил много испытаний, поэтому могут возникнуть некоторые тонкие (или не очень) проблемы.
- Я не думаю, что в C ++ возможно полностью бесшовное решение, которое удовлетворяет всем вашим требованиям без какого-либо изменения синтаксиса. (хотя я бы хотел оказаться неправым!) Надеюсь, это достаточно близко.
- Когда я исследовал этот ответ, я получил большую помощь от этой очень обширной статьи по реализации делегатов функций-членов в C ++. Любой, кто хочет узнать больше, чем они думали, может узнать об указателях на функции-члены, должен прочесть эту статью.