Как стать автором
Обновить

Вложенные функции на C++

Время на прочтение4 мин
Количество просмотров55K
Приветствую сообщество!

Я наткнулся на возможность сделать в С++ что-то похожее на объявление функций внутри функций. Выглядит это вот так:

#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=%d\n",a,b,a+b);
  } with_name(plus);

  plus(2,2);
}


В заключение отмечу, что у этих макросов не предусмотрено возвращаемого значения. Разумеется, его можно и очень просто «пробросить наверх», так что это я оставляю читателям.

P.S. Вообще-то техника изготовления вложенных классов стара как мир и я, конечно же, не открыл ничего нового. Но тем не менее, как мне кажется, такая статья полезна в образовательном смысле и обладает некоторой эстетической завершенностью.
Теги:
Хабы:
+10
Комментарии24

Публикации

Изменить настройки темы

Истории

Работа

QT разработчик
6 вакансий
Программист C++
123 вакансии

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн