Comments 36
Уже после нажатия «опубликовать» увидел кое-какие ошибки — исправил.
Не все ошибки я исправил…
Но, надеюсь, главная мысль «разделяй и властвуй» от читающих не ускользнула. Иначе говоря, главная идея была в том, чтобы используя пользовательские типы сделать из однострочных монстров читаемые определения.
Возможно, неудачно я выбрал примеры (да еще и в спешке налажал) — надо было именно об указателях на функции, как наиболее «страшные» писать.
Ну что ж, век живи — век учись, возможно, следующий блин будет не комом.
Но, надеюсь, главная мысль «разделяй и властвуй» от читающих не ускользнула. Иначе говоря, главная идея была в том, чтобы используя пользовательские типы сделать из однострочных монстров читаемые определения.
Возможно, неудачно я выбрал примеры (да еще и в спешке налажал) — надо было именно об указателях на функции, как наиболее «страшные» писать.
Ну что ж, век живи — век учись, возможно, следующий блин будет не комом.
Во-первых, вы ошиблись:
Во-вторых, в корне не согласен. Вы добавили ненужный слой абстракции над нотацией типа переменной. Каждому программисту, который прочитает ваш код, придётся распарсивать вашу собственную нотацию вида
Если не брать объявления указателей на функции (их, как раз, лучше выносить в
Читайте справа налево:
Ничего сложного.
typedef const const_int_ptr* const_ptr;
// неизменяемый указатель на предыдущий указатель
Это изменяемый указатель на неизменяемый указатель на неизменяемый int
.Во-вторых, в корне не согласен. Вы добавили ненужный слой абстракции над нотацией типа переменной. Каждому программисту, который прочитает ваш код, придётся распарсивать вашу собственную нотацию вида
const_ptr
или const_int_ptr
. Заметьте, что const
и ptr
у вас в обоих случаях значат совершенно разные вещи: в первом случае у вас константный указатель на указатель, а во втором случае — указатель на константный int
.Если не брать объявления указателей на функции (их, как раз, лучше выносить в
typedef
), Си имеет прекрасную нотацию. Главное её использовать правильно; например, писать const
справа:int const * const * const ptr;
Читайте справа налево:
ptr
— это константный указатель на константный указатель на константный int
.Ничего сложного.
Ваша статья не имеет смысла, потому что вы ошиблись в самом начале:
Все на самом деле логично: то, что находится до символа
const int *var; — указатель на переменную, которая не может менять значение.— эти 2 объявления означают абсолютно одно и то же. Чтобы получить неконстантный указатель на константные данные, нужно написать:
int const *var; — указатель, который не может менять свое значение, на переменную int, которая может…
int * const z;
Все на самом деле логично: то, что находится до символа
*
, относится к тому на что указываем, а то что после *
— к самому указателю. ДемоК чему я призываю? Да вот к чему: коллеги-программисты и те, кто хочет ими стать! Будьте проще, не кичитесь своими знаниями глубин Си
Просто не используйте костыль «const» всуе а только там, где он действительно необходим.
Полюбить очень просто. Есть такой эффект — «Стокгольмский синдром» называется. Очень помогает в подобных случаях.
Вот согласен на 100%. После некоторого времени с Си привыкаешь к указателям и прочим радостям. Потом берешь язык уровня повыше (С++ не берём, он другой и инопланетный) — и тут-то и понимаешь как тебе не хватает тех примитивных конструкций, которые можно было сложить в три этажа и обернуть скобками для лучшей читаемости, а не для компилятора…
Я верно понял, что вся статья медленно подводила нас к большой разнице между двумя очень похожими утверждениями, которые на проверку оказались идентичными?
Тогда о чем статья?
Тогда о чем статья?
Не совсем так. Посыл статьи правильный — синтаксис упрощать надо, как сделано с указателями, например, в С++. Вот только автор сначала запутался в том, что он упрощать собрался, а потом результат его упрощений оказался крайне сомнительным, да и, опять же, с ошибками.
Насчёт С++ — я имею ввиду, что в С++ можно выражать типы с указателями каким-то таким образом:
ptr<const ptr<volatile ptr<const int> > >
Что в сложных случаях будет читабельнее, чем Си вариант. Или с привкусом D:ptr ! const ptr ! volatile ptr ! const int
В C++ вообще не нужны многоэтажные объявления указателей — там есть инкапсуляция. Если проще —
const std::string& var
вместо const char* const* var
— char* скрыт в объекте std::string. Да еще и ссылка вместо указателя.Это-то всё фигня. Самое сложное — это функции, и там будет либо многоэтажные объявления, либо куча typedef-ов.
Откройте для себя std::function и не будет вам никаких многоэтажных объявлений с кучей тайпдефов.
Тот же самый typedef, вид сбоку.
Как Вы в таком случае себе представляете сферическое объявление указателя на функцию в вакууме?
Приведите пример сложного и непонятного объявления std::function
Даже в сложных случаях типа
— «функция, принимающая функцию, принимающую два аргумента типа int и возвращающую int, возвращающая функцию, принимающую int и возвращающую int» — все понятно, читабельно и логично. А как будет выглядеть это на Сишных указателях? Мне например и пробовать сейчас написать это страшно.
std::function< std::function<int (int)> (std::function<int (int, int)>) >
— «функция, принимающая функцию, принимающую два аргумента типа int и возвращающую int, возвращающая функцию, принимающую int и возвращающую int» — все понятно, читабельно и логично. А как будет выглядеть это на Сишных указателях? Мне например и пробовать сейчас написать это страшно.
«сферическое объявление указателя на функцию в вакууме»:
Выделим типы:
Если пофантазировать и сказать, что у нас весь код написан с использованием std::function, то код выглядит вот так:
void(*(*(*(*f())(void(*)()))(void(*)()))(void(*)()))();
Разбавить квалификаторами по вкусу.Выделим типы:
typedef void(*g0)();
typedef g0(*g1)(g0);
typedef g1(*g2)(g0);
typedef g2(*g3)(g0);
g3 g();
Теперь читаемо.Если пофантазировать и сказать, что у нас весь код написан с использованием std::function, то код выглядит вот так:
std::function<
std::function<
std::function<
std::function<
void()
>(std::function<void()>)
>(std::function<void()>)
>(std::function<void()>)
> h();
Я бы сказал, что никаких плюсов перед typedef в плане выразительности std::function не представляет. Наоборот, я мало того, что указываю, что тип — функция, я ещё и указываю сигнатуру функции рядом используя Си нотацию. Многословненько.В вашем примере для читающего код сигнатуры скрыты за кучей typedef, и без их разбора ему вообще не понятно, что таки туда передавать. Если std::function кажется слишком длинно, можете с помощью using хоть fn ее называть, и будет
fn< fn<int (int)> ( fn<int (int, int)> ) >
Дело не в том, что длинно. Дело в том, что используя std::function я пишу то, что я бы написал, используя typedef или инлайн-нотацию, ПЛЮС описываю непосредственно шаблон.
Как я выше сказал, без описания сигнатуры функции это вообще не нужно. А в данном случае читающий сразу понимает, что куда передается и возвращается. Без хождения по тайпдефам и запоминания каждого, и без парсинга скобок и звездочек.
Видимо, вы просто фанат std::function. :) На мой взгляд, вариант с std::function, в котором почти в два раза больше скобочек и ровно в два раза больше упоминаний функций, выглядит достаточно уродливо. Я бы использовал typedef.
Стоит признать, что
std::function
это не равнозначная замена, у нее есть рантайм издержки.Она не хранит в себе ничего кроме указателя, и издержки такие же. А clang их даже инлайнить умеет.
Она type-erased. Хранит указатель на базовый класс с виртуальным operator(). Поэтому один лишний косвенный вызов нужен. (Это сделано, чтобы можно было хранить любые callable: помимо указателей на функции, это может быть например указатель на метод или функциональный объект)
И получаем аллокацию памяти на ровном месте, где она, быть может, не нужна ещё.
Sign up to leave a comment.
Будь проще, и люди потянутся к тебе, или как полюбить Си, сохранив рассудок