Запуск функции на С++?
С "hooking" я имею в виду способность не навязчиво переопределять поведение функции. Некоторые примеры:
- Распечатайте сообщение журнала до и/или после тела функции.
- Оберните тело функции в тело try try.
- Длительность измерения функции
- и т.д...
Я видел различные реализации в различных языках программирования и библиотеках:
- Аспектно-ориентированное программирование
- Функции первого класса JavaScript
- Декоратор ООП
- подклассы WinAPI
- Ruby
method_missing
- SWIG
%exception
ключевое слово, которое предназначено для обертывания всех функций в блоке try/catch, может быть (ab) использовано для цель подключения
Мои вопросы:
- IMO - такая невероятно полезная функция, что мне интересно, почему она никогда не была реализована как функция языка С++. Существуют ли какие-либо причины, препятствующие тому, чтобы это стало возможным?
- Каковы рекомендуемые методы или библиотеки для реализации этого в программе на С++?
Ответы
Ответ 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, которые позволяют вам изменять эти вещи во время выполнения.
-
Потому что это подорвет систему типов. Что означает, что функция является частной или не виртуальной, если кто-то может переопределить ее поведение?
-
Чтение сильно пострадает. Любая функция может иметь свое поведение, переопределенное где-то еще в коде! Чем больше контекста вам нужно понять, что делает функция, тем сложнее определить большую базу кода. Хокинг - это ошибка, а не особенность. По крайней мере, если вам необходимо прочитать то, что вы написали несколько месяцев спустя.