универсальная неразрушающая обертка кэша

синтаксис JavaScript

  1. Функционального объекта:

    f = new Function( "foo", "bar", "return foo+bar;" );
    

    (Это берет n аргументы, первые n-1 являются аргументами в пользу функции, тогда энный фактическая функция, в строковой форме. Который просто глуп.)

  2. Аргументы функции могут быть повторены.

    f = new Function( "foo", "foo", "return foo;" );
    

    последнее повторение является единственным, когда-либо используемым, хотя:

    f( "bye", "hi" ) // returns "hi"
    f( "hi" ) // returns undefined
    
  3. E4X должен просто умереть. Мои пользователи всегда жалуются, что это не прокладывает себе путь, они думают, что это будет. Давайте столкнемся с ним при необходимости в полутора страницах psuedocode для метода set пора заново продумать вещи.

  4. понятие стандарта А stdin/stdout/stderr (и файлы!) было бы хорошо.

  5. пустой указатель! = неопределенный

    Это является раздражающим для обработки их обоих. Иногда это полезно, но большинству языков удается хромать вдоль штрафа с одним.

5
задан bk1e 10 August 2009 в 16:29
поделиться

5 ответов

Я думаю, что у меня есть ответ, который вы ищете, или, по крайней мере, я почти знаю. Он использует стиль отправки, который вы предложили глупо, но я думаю, что он соответствует первым двум критериям, которые вы указали, и более или менее соответствует третьему.

  1. Класс упаковки вообще не нужно изменять.
  2. Он вообще не модифицирует обернутый класс.
  3. Он только изменяет синтаксис, вводя функцию диспетчеризации.

Основная идея состоит в том, чтобы создать шаблонный класс, параметром которого является класс объекта, который нужно обернуть, с помощью метода шаблона 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

ПРИМЕЧАНИЯ

  • Это работает только для методов, которые принимают 1 аргумент и возвращают результат. Это не работает для методов, которые принимают 0 аргументов, или 2, или 3, или более аргументов. Однако это не должно быть большой проблемой. Вы можете реализовать перегруженные версии диспетчеризации, которые принимают разное количество аргументов до некоторого разумного максимума. Это то, что делает библиотека Boost Tuple . Они реализуют кортежи, содержащие до 10 элементов, и предполагают, что большинству программистов не нужно больше этого.
  • Возможность реализации нескольких перегрузок для диспетчеризации - вот почему я использовал шаблон предиката FirstEq с алгоритмом find_if вместо простого поиска по циклу. . Это немного больше кода для одноразового использования, но если вы собираетесь выполнить аналогичный поиск несколько раз, в итоге будет меньше кода в целом и меньше шансов получить неуловимую ошибку в одном из циклов.
  • Это не работает для методов, ничего не возвращающих, т.е. void , но если метод ничего не возвращает, тогда вам не нужно кэшировать результат!
  • Он не работает для функций-членов шаблона обернутого класса, потому что вам нужно передать фактическую функцию-член указатель на отправку, а неустановленная функция-шаблон не имеет указателя (пока). Возможно, есть способ обойти это, но я еще не очень много пробовал.
  • Я еще не проводил большого тестирования этого, поэтому могут возникнуть некоторые тонкие (или не очень тонкие) проблемы.
  • Я не Я думаю, что на C ++ возможно полностью бесшовное решение, которое удовлетворяет всем вашим требованиям без каких-либо изменений синтаксиса. (хотя я бы хотел, чтобы меня доказали, что он неправ!). Надеюсь, это достаточно близко.
  • Когда я исследовал этот ответ, мне очень помогли эта очень обширная статья о реализации делегатов функций-членов в C ++. Любой, кто хочет узнать об указателях на функции-члены больше, чем они думали, должен внимательно прочитать эту статью.
1
ответ дан 15 December 2019 в 06:32
поделиться

Я не думаю, что это может можно легко выполнить, используя только оболочку, поскольку вам придется перехватывать вызовы ввода-вывода, поэтому упаковка класса поместит код на неправильный уровень. По сути, вы хотите подставить код ввода-вывода под объектом, но вы пытаетесь сделать это с верхнего уровня. Если вы думаете о коде как о луковице, вы пытаетесь изменить внешнюю оболочку, чтобы повлиять на что-то в двух или трех слоях; IMHO, предполагающий, что дизайн может потребовать переосмысления.

Если класс, который вы пытаетесь обернуть / изменить таким образом, действительно позволяет вам передавать поток (или любой другой механизм ввода-вывода, который вы используете), то замените его на кэширование одного было бы правильным решением; по сути, это то, чего вы пытались бы достичь с помощью своей оболочки.

1
ответ дан 15 December 2019 в 06:32
поделиться

Это похоже на простую задачу, если предположить, что "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);
   }
};
1
ответ дан 15 December 2019 в 06:32
поделиться

Я думаю, что вам нужно что-то вроде прокси / декоратора (шаблоны проектирования). Вы можете использовать шаблоны, если вам не нужна динамическая часть этих шаблонов. Дело в том, что вам нужно хорошо определить интерфейс, который вам понадобится.

0
ответ дан 15 December 2019 в 06:32
поделиться

Я не разобрался в случае обработки объектных методов, но думаю, что у меня есть хорошее исправление для обычных функций

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;
    }
}

Любое советы, как заставить это работать для объектов?

0
ответ дан 15 December 2019 в 06:32
поделиться
Другие вопросы по тегам:

Похожие вопросы: