синтаксис JavaScript
Функционального объекта:
f = new Function( "foo", "bar", "return foo+bar;" );
(Это берет n аргументы, первые n-1 являются аргументами в пользу функции, тогда энный фактическая функция, в строковой форме. Который просто глуп.)
Аргументы функции могут быть повторены.
f = new Function( "foo", "foo", "return foo;" );
последнее повторение является единственным, когда-либо используемым, хотя:
f( "bye", "hi" ) // returns "hi"
f( "hi" ) // returns undefined
E4X должен просто умереть. Мои пользователи всегда жалуются, что это не прокладывает себе путь, они думают, что это будет. Давайте столкнемся с ним при необходимости в полутора страницах psuedocode для метода set пора заново продумать вещи.
понятие стандарта А stdin/stdout/stderr (и файлы!) было бы хорошо.
пустой указатель! = неопределенный
Это является раздражающим для обработки их обоих. Иногда это полезно, но большинству языков удается хромать вдоль штрафа с одним.
Я думаю, что у меня есть ответ, который вы ищете, или, по крайней мере, я почти знаю. Он использует стиль отправки, который вы предложили глупо, но я думаю, что он соответствует первым двум критериям, которые вы указали, и более или менее соответствует третьему.
Основная идея состоит в том, чтобы создать шаблонный класс, параметром которого является класс объекта, который нужно обернуть, с помощью метода шаблона dispatch
, параметры которого являются аргументом и типами возвращаемого значения функции-члена. Метод диспетчеризации просматривает переданный указатель на функцию-член, чтобы узнать, вызывался ли он раньше. Если это так, он извлекает запись предыдущих аргументов метода и вычисленных результатов, чтобы вернуть ранее вычисленное значение для аргумента, переданного для отправки, или для его вычисления, если он новый.
Поскольку то, что делает этот класс упаковки, также называется memoization , я решил называть шаблон 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
void
, но если метод ничего не возвращает, тогда вам не нужно кэшировать результат! Я не думаю, что это может можно легко выполнить, используя только оболочку, поскольку вам придется перехватывать вызовы ввода-вывода, поэтому упаковка класса поместит код на неправильный уровень. По сути, вы хотите подставить код ввода-вывода под объектом, но вы пытаетесь сделать это с верхнего уровня. Если вы думаете о коде как о луковице, вы пытаетесь изменить внешнюю оболочку, чтобы повлиять на что-то в двух или трех слоях; IMHO, предполагающий, что дизайн может потребовать переосмысления.
Если класс, который вы пытаетесь обернуть / изменить таким образом, действительно позволяет вам передавать поток (или любой другой механизм ввода-вывода, который вы используете), то замените его на кэширование одного было бы правильным решением; по сути, это то, чего вы пытались бы достичь с помощью своей оболочки.
Это похоже на простую задачу, если предположить, что "NumberCruncher" имеет известный интерфейс, скажем, int operator (int). Обратите внимание, что вам нужно усложнить поддержку других интерфейсов. Для этого я добавляю еще один параметр шаблона, адаптер. Адаптер должен преобразовать некоторый интерфейс в известный интерфейс. Вот простая и глупая реализация со статическим методом, который является одним из способов сделать это. Также посмотрите, что такое Functor.
struct Adaptor1 {
static int invoke(Cached1 & c, int input) {
return(c.foo1(input));
}
};
struct Adaptor2 {
static int invoke(Cached2 & c, int input) {
return(c.foo2(input));
}
};
template class CacheWrapper<typename T, typeneame Adaptor>
{
private:
T m_cachedObj;
std::map<int, int> m_cache;
public:
// add c'tor here
int calculate(int input) {
std::map<int, int>::const_iterator it = m_cache.find(input);
if (it != m_cache.end()) {
return(it->second);
}
int res = Adaptor::invoke(m_cachedObj, input);
m_cache[input] = res;
return(res);
}
};
Я думаю, что вам нужно что-то вроде прокси / декоратора (шаблоны проектирования). Вы можете использовать шаблоны, если вам не нужна динамическая часть этих шаблонов. Дело в том, что вам нужно хорошо определить интерфейс, который вам понадобится.
Я не разобрался в случае обработки объектных методов, но думаю, что у меня есть хорошее исправление для обычных функций
template <typename input_t, typename output_t>
class CacheWrapper
{
public:
CacheWrapper (boost::function<output_t (input_t)> f)
: _func(f)
{}
output_t operator() (const input_t& in)
{
if (in != input_)
{
input_ = in;
output_ = _func(in);
}
return output_;
}
private:
boost::function<output_t (input_t)> _func;
input_t input_;
output_t output_;
};
, которое можно использовать следующим образом:
#include <iostream>
#include "CacheWrapper.h"
double squareit(double x)
{
std::cout << "computing" << std::endl;
return x*x;
}
int main (int argc, char** argv)
{
CacheWrapper<double,double> cached_squareit(squareit);
for (int i=0; i<10; i++)
{
std::cout << cached_squareit (10) << std::endl;
}
}
Любое советы, как заставить это работать для объектов?