Приветствую сообщество!
Я наткнулся на возможность сделать в С++ что-то похожее на объявление функций внутри функций. Выглядит это вот так:
#include <iostream>
int main()
{
inline_function(std::string s)
{
std::cout << "Hello, " << s << "!n";
}
with_name(make_hello);
make_hello("Vasiliy Pupkin!");
return 0;
}
В приведенном примере внутри метода main изготавливается вложенный «метод» с названием make_hello и затем вызывается с параметром «Vasiliy Pupkin». Разумеется, на экран будет выведено Hello, Vasiliy Pupkin!
.
К сожалению, перетащить название вверх у меня не получилось.
Сделано это, конечно же, на макросах.
Примеры того же самого без макросов
В C++ все-таки нету вложенных функций, поэтому нам придется эмулировать их синтаксис чем-либо еще. Поэтому зададимся другим вопросом: что именно выглядит так же, как функция, но не функция?
Ответа целых два: это, во-первых, вызов конструктора класса без с созданием объекта «в пустоту»:
#include <stdio.h>
int main()
{
// Вместо функции мы изготовим класс с конструктором
class inline_function
{
public:
// Вызов конструктора будет синтаксически неотличим от вызова вложенного метода
inline_function()
{
printf("Hello, hell!n");
}
};
// создание объекта и вызов конструктора
inline_function();
return 0;
}
К сожалению, есть два существенных «но», которые не позволяют использовать приведенный пример на практике:
1. Мы создаем по одному новому объекту на каждый вызов функции, что не есть хорошо. А ну как у меня цикл на много вызовов? Накладные расходы можно стерпеть на факт декларации такой штуковины, но никак не на использование.
2. Visual Studio со включенной оптимизацией компилятора просто-напросто вырежет создание inline_function() как бесполезное. Логика компилятора понятна (все равно этот создаваемый объект никто не будет использовать, так зачем его создавать?) но представляет опасность.
Однако есть еще штука под названием «переопределение операторов» — и мы можем переопределить двойные скобочки (по умному переопределенный оператор () называется «функтором», во как):
#include <stdio.h>
int main()
{
// Вместо функции мы изготовим класс переопределенным оператором
class inline_function_class
{
public:
void operator()()
{
printf("Hello, world!n");
}
};
// Создадим объект только что созданного типа -- всего один раз!
inline_function_class inline_function;
// И вызовем функтор
inline_function();
return 0;
}
В этом примере уже почти все хорошо, кроме того, что класс можно сделать и анонимным, чтобы попусту не трепать имя вида inline_function_class
#include <stdio.h>
int main()
{
// Вместо функции мы изготовим класс переопределенным оператором
class
{
public:
void operator()()
{
printf("Hello, world!n");
}
} inline_function; // Декларация и объявление в одном флаконе
// И вызовем функтор
inline_function();
return 0;
}
Пишем макросы
Итак, цель — в принципе — достигнута: у нас есть способ изолировать кусок кода внутри одного метода так, чтобы не подпортить внешнюю зону видимости. Но есть один минус: во всех приведенных примерах — на мой вкус — слишком много букв. Поэтому мы разделим наш код на три части:
#include <stdio.h>
int main()
{
// Вместо функции мы изготовим класс переопределенным оператором
/** первая часть -- до имплементации функции, которая заранее известна, если не говорить о параметрах: **/
class
{
public:
void operator()(/* а тут ведь могут быть и параметры*/)
{
/**вторая часть -- собственно, тело функции, которое будет писать человек **/
printf("Hello, world!n");
/** Третья часть -- завершающая часть класса **/
}
} inline_function; // Декларация и объявление в одном флаконе
/** =============================== **/
// И вызовем функтор
inline_function();
return 0;
}
Первую и вторую части можно запихнуть в два макроса, дав им какие-нибудь красивые имена. Например,
#define inline_function(params)
class
{
public: void operator() (params)
{
#define with_name(value)
}
} value;
#define with_params(...) __VA_ARGS__ // А это-то зачем тут? Читай ниже.
При помощи этих макросов код «вложенной функции» значительно упрощается на вид (хоть и выглядит не в С++ стиле):
int main()
{
inline_function(char * name)
{
printf("Hello, %s!n",name);
} with_name(hello)
hello("Pupkin");
}
К сожалению, все портится, если попробовать задать не один параметр у функции, а хотя бы два. В этом случае компилятор нажалуется, мол, в макросе inline_function всего ОДИН параметр и баста. Проблему можно решить, использовав макрос __VA_ARGS__ (который, хоть и не входит в стандарт, но поддерживается всеми реально используемыми компиляторами).
Чуть сложнее, но все еще приемлимо будет выглядеть функция с двумя параметрами:
int main()
{
inline_function (with_params(int a, int b))
{
printf("%d+%d=%dn",a,b,a+b);
} with_name(plus);
plus(2,2);
}
В заключение отмечу, что у этих макросов не предусмотрено возвращаемого значения. Разумеется, его можно и очень просто «пробросить наверх», так что это я оставляю читателям.
P.S. Вообще-то техника изготовления вложенных классов стара как мир и я, конечно же, не открыл ничего нового. Но тем не менее, как мне кажется, такая статья полезна в образовательном смысле и обладает некоторой эстетической завершенностью.
Автор: Arenim