Подтвердить что ты не робот

Запуск функции на С++?

С "hooking" я имею в виду способность не навязчиво переопределять поведение функции. Некоторые примеры:

  • Распечатайте сообщение журнала до и/или после тела функции.
  • Оберните тело функции в тело try try.
  • Длительность измерения функции
  • и т.д...

Я видел различные реализации в различных языках программирования и библиотеках:

  • Аспектно-ориентированное программирование
  • Функции первого класса JavaScript
  • Декоратор ООП
  • подклассы WinAPI
  • Ruby method_missing
  • SWIG %exception ключевое слово, которое предназначено для обертывания всех функций в блоке try/catch, может быть (ab) использовано для цель подключения

Мои вопросы:

  • IMO - такая невероятно полезная функция, что мне интересно, почему она никогда не была реализована как функция языка С++. Существуют ли какие-либо причины, препятствующие тому, чтобы это стало возможным?
  • Каковы рекомендуемые методы или библиотеки для реализации этого в программе на С++?
4b9b3361

Ответ 1

Если вы говорите о вызове нового метода до/после тела функции, без изменения тела функции, вы можете его основать на this, в котором используется пользовательский shared_ptr deleter для запуска функции после тела. Он не может использоваться для try/catch, так как до и после должны быть отдельные функции, используя эту технику.

Кроме того, в приведенной ниже версии используется shared_ptr, но с С++ 11 вы можете использовать unique_ptr для получения такого же эффекта без затрат на создание и уничтожение общего указателя каждый раз, когда вы его используете.

#include <iostream>
#include <boost/chrono/chrono.hpp>
#include <boost/chrono/system_clocks.hpp>
#include <boost/shared_ptr.hpp>

template <typename T, typename Derived>
class base_wrapper
{
protected:
  typedef T wrapped_type;

  Derived* self() {
    return static_cast<Derived*>(this);
  }

  wrapped_type* p;

  struct suffix_wrapper
  {
    Derived* d;
    suffix_wrapper(Derived* d): d(d) {};
    void operator()(wrapped_type* p)
    {
      d->suffix(p);
    }
  };
public:
  explicit base_wrapper(wrapped_type* p) :  p(p) {};


  void prefix(wrapped_type* p) {
     // Default does nothing
  };

  void suffix(wrapped_type* p) {
     // Default does nothing
  }

  boost::shared_ptr<wrapped_type> operator->() 
  {
    self()->prefix(p);
    return boost::shared_ptr<wrapped_type>(p,suffix_wrapper(self()));
  }
};




template<typename T>
class timing_wrapper : public base_wrapper< T, timing_wrapper<T> >
{
  typedef  base_wrapper< T, timing_wrapper<T> > base;
  typedef boost::chrono::time_point<boost::chrono::system_clock, boost::chrono::duration<double> > time_point;

  time_point begin;
public:
  timing_wrapper(T* p): base(p) {}


  void prefix(T* p) 
  {
    begin = boost::chrono::system_clock::now();
  }

  void suffix(T* p)
  {
    time_point end = boost::chrono::system_clock::now();

    std::cout << "Time: " << (end-begin).count() << std::endl;
  }
};

template <typename T>
class logging_wrapper : public base_wrapper< T, logging_wrapper<T> >
{
  typedef  base_wrapper< T, logging_wrapper<T> > base;
public:
  logging_wrapper(T* p): base(p) {}

  void prefix(T* p) 
  {
    std::cout << "entering" << std::endl;
  }

  void suffix(T* p) 
  {
    std::cout << "exiting" << std::endl;
  }

};


template <template <typename> class wrapper, typename T> 
wrapper<T> make_wrapper(T* p) 
{
  return wrapper<T>(p);
}


class X 
{
public:
  void f()  const
  {
    sleep(1);
  }

  void g() const
  {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }

};



int main () {

  X x1;


  make_wrapper<timing_wrapper>(&x1)->f();

  make_wrapper<logging_wrapper>(&x1)->g();
  return 0;
}

Ответ 2

Существуют специфические для компилятора функции, которые вы можете использовать, например, GCC - функции finstrument. Другие компиляторы, вероятно, будут иметь аналогичные функции. Дополнительную информацию см. В этом вопросе fooobar.com/questions/163919/....

Другой подход - использовать что-то вроде технику упаковки Bjarne Stroustrup.

Ответ 3

Чтобы ответить на свой первый вопрос:

  • Большинство динамических языков имеют свои конструкторы method_missing, PHP имеет магические методы (__call и __callStatic), а Python имеет __getattr__. Я думаю, причина, по которой это не доступно на С++, идет вразрез с типизированной природой С++. Реализация этого в классе означает, что любые опечатки в конечном итоге вызовут эту функцию (во время выполнения!), Что предотвращает улов этих проблем во время компиляции. Смешивание С++ с утиной печатью не кажется хорошей идеей.
  • С++ пытается быть как можно быстрее, поэтому функции первого класса не могут быть и речи.
  • АОП. Теперь это более интересно, по-технически нет ничего, что помешало бы этому добавлению к стандарту С++ (кроме того, что добавление другого уровня сложности к уже чрезвычайно сложному стандарту, возможно, не очень хорошая идея). На самом деле есть компиляторы, способные волновать код, AspectС++ является одним из них. Год назад или около того он был нестабильным, но похоже, что с тех пор им удалось выпустить 1.0 с довольно приличным набором тестов, чтобы он мог выполнять эту работу сейчас.

Есть несколько методов, здесь возникает связанный с этим вопрос:

Эмуляция CLOS: before,: after и: around в С++.

Ответ 4

IMO - это невероятно полезная функция, так почему же это не язык С++? Существуют ли какие-либо причины, препятствующие тому, чтобы это стало возможным?

С++ язык не предоставляет никаких средств для этого напрямую. Однако это также не создает никаких прямых ограничений для этого (AFAIK). Этот тип функции проще реализовать в интерпретаторе, чем в собственном коде, поскольку интерпретация - это часть программного обеспечения, а не инструкции для потоковой передачи процессора. Вы могли бы предоставить интерпретатор С++ с поддержкой перехвата, если хотите.

Проблема в том, почему люди используют С++. Многие люди используют С++, потому что им нужна максимальная скорость выполнения. Для достижения этой цели компиляторы выписывают собственный код в предпочтительном формате операционной системы и пытаются записать в файл как можно больше файлов в скомпилированный исполняемый файл. Последняя часть часто означает вычисления адресов в момент компиляции/ссылки. Если вы исправите адрес функции в это время (или, что еще хуже, включите тело функции), то больше нет поддержки для перехватов.

Как говорится, есть способы сделать дешевое подключение, но он требует расширения компилятора и полностью не переносится. Раймонд Чен рассказал о том, как горячее исправление реализовано в Windows API. Он также рекомендует не использовать его в обычном коде.

Ответ 5

По крайней мере, в среде С++, которую я использую, предоставляется набор чистых виртуальных классов

class RunManager;
class PhysicsManager;
// ...

Каждый из них определил набор действий

void PreRunAction();
void RunStartAction()
void RunStopAction();
void PostRunAction();

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

Объедините это с условной компиляцией (да, я знаю "Юк!" ), и вы можете получить то, что хотите.

Ответ 6

Это не С++, но для выполнения некоторых вещей, которые вы упомянули, я использовал переменную среды LD_PRELOAD в системах * nix. Хорошим примером этой техники в действии является библиотека faketime, которая перехватывает функции времени.

Ответ 7

  • Должен быть способ реализовать функциональность, не влияя на производительность кода, который не использует функциональность. С++ разработан по принципу, что вы оплачиваете только затраты на использование функций, которые вы используете. Вставка, если проверка в каждой функции, чтобы проверить, была ли она переопределена, была бы неприемлемо медленной для многих проектов на С++. В частности, это делает работу таким образом, что нет никакой стоимости исполнения при одновременном использовании независимой компиляции переопределенных и переопределяющих функций. Если вы разрешаете только переопределять время компиляции, тогда это проще сделать с успехом (компоновщик может позаботиться о переписывании адресов), но вы сравниваете с ruby ​​и javascript, которые позволяют вам изменять эти вещи во время выполнения.

  • Потому что это подорвет систему типов. Что означает, что функция является частной или не виртуальной, если кто-то может переопределить ее поведение?

  • Чтение сильно пострадает. Любая функция может иметь свое поведение, переопределенное где-то еще в коде! Чем больше контекста вам нужно понять, что делает функция, тем сложнее определить большую базу кода. Хокинг - это ошибка, а не особенность. По крайней мере, если вам необходимо прочитать то, что вы написали несколько месяцев спустя.