ch
Feedback
Грокаем C++

Грокаем C++

前往频道在 Telegram

Два сеньора C++ - Владимир и Денис - отныне ваши гиды в этом дремучем мире плюсов. По всем вопросам (+ реклама) @ninjatelegramm Менеджер: @Spiral_Yuri Реклама: https://telega.in/c/grokaemcpp Мы на TGstat: https://tgstat.ru/channel/@grokaemcpp/stat

显示更多
9 384
订阅者
+924 小时
+147
+1430
帖子存档
​​Виртуальная дружественная функция #новичкам Вчера мы поговорили о том, что таких функций не бывает, но очень хочется получить альтернативу. Хочется изменять работу функции в зависимости от динамического типа, который в нее передается. Ну и на самом деле ничего сложного здесь нет. Можно ведь сделать полиморфный метод, который будет вызываться в этой функции. И вот его поведение мы можем изменять примерно как нашей душеньке захочется. Рассмотрим банальный пример - сериализация объекта в поток. Обычно для такой задачи используют дружественный оператор <<. Но вот хотелось бы сериализовать в зависимости от динамического типа. Фигня вопрос.
struct Person {
  Person(const std::string &first_name,
       const std::string &last_name) : first_name_{first_name},
                      last_name_{last_name} {}
  
  friend std::ostream& operator<<(std::ostream& o, const Person& b) {
    return o << b.to_str();
  }
protected:
  virtual std::string to_str() const{
    return first_name_ + " " + last_name_;
  }
private:
  std::string first_name_;
  std::string last_name_;
};

struct TaxPayer : Person {
  TaxPayer(const std::string first_name,
       const std::string last_name,
       const std::string itn) : Person{first_name, last_name}, itn_{itn} {}
protected:
  virtual std::string to_str() const{
    return Person::to_str() + " " + itn_;
  }
private:
  std::string itn_;
};

int main() {
  auto prs1 = std::make_unique<Person>("Ter", "Minator");
  auto prs2 = std::make_unique<TaxPayer>("Ace", "Ventura", "0000");
  std::cout << *prs1 << std::endl;
  std::cout << *prs2 << std::endl;
}
// OUTPUT:
// Ter Minator
// Ace Ventura 0000
Есть у нас класс человека с его именем и фамилией. Есть класс налогоплательщика, который наследуется от человека и добавляет к полям ИНН. Допустим, мы хотим как-то выводить на консоль содержимое этих людей(в смысле поля; расчленением мы не занимаемся, не в Питере живем все-таки). Можно конечно в каждом классе определять свой оператор или выводить на консоль результат работы метода to_str, но это лишний апи и лишний код. Мы просто в базовом классе определяем другана и говорим, чтобы он вызывал виртуальный метод. И все. Да, это очень просто. Но подход скрытия деталей реализации за закрытым виртуальным методом используется, например в идиоме невиртуального интерфейса и еще невесть где. Поэтому о даже о таком приеме надо знать и применять его в подходящих ситуациях. Have a real friends. Stay cool. #cppcore

​​Может ли дружественная функция быть виртуальной? #опытным Еще один вопрос из серии "а могут ли рыбы быть рыжими?" - "Да вроде как по цвету могут, но рыжие это обычно про волосы, а волос у рыб нет. Да и вообще, кому это знание нужно?!". Что-то типа такого. Но раз такие вопросы задают, то пора в ваше кунг-фу добавить и этот приемчик. Давайте начнем с начала. Дружественная функция - обычная функция, которая имеет доступ к полям класса-друга. И все. Никаких других особенностей у нее нет. И ограничений тоже никаких нет. Ее сигнатура может быть любой. Объект класса-друга может в ней вообще не фигурировать. А для виртуальности необходим экземпляр класса, в котором укромно затаился vptr, который и отвечает за диспатчинг вызовов в рантайме. Вот и получается, что виртуальных дружественных функций не бывает. Конечно вопрос о виртуальных friend-функциях может заходить только тогда, когда в параметрах есть ссылка или указатель на объект полиморфного класса. Но в общем случае на это не накладываются ограничения. Поэтому и в общем случае виртуальных подруг у класса не бывает. Теперь сможете смело парировать интервьюеров четким ударом в глазницу быстрым точным ответом Но осадочек-то остался. Хочется менять работу дружественной функции в зависимости от того, какой конкретный объект ей передается. Такое мероприятие можно организовать достаточно просто, но это в следующем посте. Have a real friends. Stay cool. #interview #cppcore

Приглашаем на митап для бэкенд-разработчиков от Еком-сервисов Яндекса В Минск приехал Яндекс Foodtech Tour — серия митапов в
Приглашаем на митап для бэкенд-разработчиков от Еком-сервисов Яндекса В Минск приехал Яндекс Foodtech Tour — серия митапов в столицах, на которых эксперты Еды, Лавки и Маркета рассказывают о внутренней кухне разработки сервисов. В Минске спикеры расскажут о core-технологиях, которые лежат в основе работы продуктов. Митап пройдет 7 декабря.  Программа насыщенная:  👉Доклады о BDUI и ускорении разработки. Никита Шумский из Еды расскажет об особенной инфраструктуры Еды, различиях классического и мобильного бэкенда и преимуществах BDUI. Ваня Ходор из Лавки поделится кейсом ускорения разработки, причем не скорости работы кода, а его написания. 👉CaseLab о мультизаказе в Еде. Это интерактивный формат, в котором участники разбирают реальный кейс из работы сервиса, предлагают решение и получают фидбек от экспертов Яндекса. 👉Нетворкинг и afterparty Будет интересно — зовите друзей и регистрируйтесьКоличество мест ограничено. Дождитесь подтверждения заявки.

​​BUG Еще одно слово, которое мы постоянно используем, вроде как знаем его значение, но понятие не имеем о его ориджине. Даже те, кто знает английский, не сразу доходят до скрытых взаимосвязей. Дело в том, что на инглише "bug" - это жук и иногда какое-то обобщенное название рандомного насекомого. И как жуки связаны с программными ошибками? Словом "баг" еще в позапрошлом столетии нарекали какие-то неполадки с электротехникой. В те времена эти технологии были "новинкой" и страдали от всяких детских проблем. Поэтому скорее всего эти проблемы сравнивали с жуками из-за их схожести с маленькими, надоедливыми насекомыми, мешающими работать. Они постоянно где-то прячутся, неожиданно появляются и непонятно откуда вылезают. Томас Эдисон писал:
Так было со всеми моими изобретениями. Первый шаг — интуиция, которая приходит как вспышка, затем возникают трудности — устройство отказывается работать, и именно тогда проявляются „жучки“ — как называют эти мелкие ошибки и трудности — и требуются месяцы пристального наблюдения, исследований и усилий, прежде чем дело дойдёт до коммерческого успеха или неудачи.
Поэтому такое значение слова было в принципе всегда известно. Но по отношению к программной ошибке термин стал употребляться после одного знаменитого случая(по одной из версий).  В 1946 году в работе компьютера Марк-2 ( Harvard Mark-II) обнаружили ошибку. Тогда ЭВМ были размером с баттхертом либералов после победы Трампа. Все работало на больших элементах, лампах и реле и время от времени какой-то из элементов выходил из строя. Это вполне нормальная тема для тех лет. И в этот раз научный сотрудник Грейс Хоппер отследила проблему и была удивлена, обнаружив сгоревшего мотылька, попавшего на контакты. Бездыханный трупик мотылька был извлечен и приклеен к отчету липкой лентой с комментарием «First actual case of bug being found.» («Первый реальный случай нахождения жучка»). Press F. После этого случая термин разлетелся из уст в уста. Тогда программисты занимались не только софтиной, но и плотно работали с железками и, в принципе, с электроникой. Им уже было знакомо называть багами проблемы в технике. Теперь к значению добавились и программные ошибки. Так этот жук стал самым знаменитым жуком в мире и увековечил память о себе в умах программистов и не только.   Можно кстати сделать один небольшой шажок вперед и понять, что примерно оттуда же растут ноги у слова «дебаггер» (debugger) – буквально «избавитель от жучков». Так что у нас есть что-то общее с дезинсекторами... Dig to the origin. Stay cool. #fun

​​Различаем преинкремент и постинкремент #новичкам Новичкам всегда не просто даются перегрузки операторов. Не то, чтобы кому-то часто приходится перегружать операторы. Но уж эти *нкременты точно редко. Из-за этого особенности постоянно забываются. Давайте же во всем разберемся. Есть операторы инкремента и декремента. Первые увеличивают значение на какую-то условную единицк, вторые - уменьшаю. Они подразделяются на 2 формы: пре- и пост-. Далее будем все разбирать на примере операторов инкремента, для декремента все аналогично, только меняете плюс на минус. Преинкремент - увеличивает значение на единицу и возвращает ссылку на уже увеличенное значение. Синтаксис использования такой: ++value. Постинкремент - делает копию исходного числа, увеличивает на единицу оригинал, и возвращает копию по значению. Синтаксис использования такой: value++. Обычно внешний вид перегрузки операторов внутри описания класса такой: возвращаемое_значение operator{символы_вызова_оператора}(аргументы). Например, оператор копирования вызывается с помощью оператора присваивания(=). Поэтому подставляем в шаблон на место возвращаемое_значение ссылку на объект, на место символы_вызова_оператора подставляем =, на место аргументов - const Type&. Получается так:
Type& operator=(const Type&);
Но теперь возникает вопрос. С++ не различает функции, у которых то же имя, то же те же аргументы, но разные возвращаемые значения. У нас как раз такая ситуация: названия одинаковые(operator++), возвращаемые значения разные(в одном случае возвращаем по ссылке, во втором - по значению) и одинаково отсутствуют аргументы. Если бы мы определили операторы так:
Type& operator++() {
  value_ += 1;
  return *this;
}

Type operator++() {
  auto tmp_value = *this;
  value_ += 1;
  return tmp_value;
}
То была бы примерно такая ошибка компиляции: functions that differ only in their return type cannot be overloaded. Что же делать? Примерно таким же вопросом задался Страуструп и сделал ход конем(правда конь ходил на костылях). Он связал префиксный оператор с человеческой формой объявления, а постфиксный - с вот такой:
Type operator++(int) {
  auto tmp_value = *this;
  value_ += 1;
  return tmp_value;
}
Он ввел безымянный интовый параметр функции. Теперь чисто технически, компилятор сможет различить 2 вызова этих операторов и правильно заметчить эти вызовы на нужные определения. Если видит префикс - выбирает оператор без аргумента, видит постфикс - выбирает с аргументом. Вопрос, почему тип аргумента именно int - остается открытым. Наверное так было проще. Итого, так перегружаются операторы инкремента:
struct Type {
  Type& operator++() {
    value_ += 1;
    return *this;
  }
  
  Type operator++(int) {
    auto tmp_value = *this;
    value_ += 1;
    return tmp_value;
  }
private:
  IntegerLikeType value_;
};
Think of your decisions twice. Stay cool. #cppcore

Яндекс Foodtech Tour теперь в Казани! Эксперты Еком-сервисов Яндекс продолжают тур по городам с митапами для бэкенд-разработч
Яндекс Foodtech Tour теперь в Казани! Эксперты Еком-сервисов Яндекс продолжают тур по городам с митапами для бэкенд-разработчиков. Ближайший пройдет в Казани уже 14 ноября. В программе три доклада:  👉 Никита Сидоров, руководитель службы инфраструктуры пользовательской скорости в Яндекс маркете, расскажет про подходы к работе над перформансом приложения 👉 Гадель Закиров и Назар Старанцов, руководители групп в Яндекс Еде, объяснят, как можно ускорять старт приложения и загрузку главной страницы  👉 Гоша Пономарев и Костя Захматов, разработчики в Яндекс Лавке, поделятся историей ускорения работы в дарксторах  После докладов гостей ждет открытый микрофон со спикерами и афтепати. Зовите друзей и регистрируйтесьОбратите внимание, количество мест ограничено. После регистрации обязательно дождитесь подтверждения заявки.

Принтуем уникальный указатель #новичкам Умные указатели делают нашу жизнь намного проще. Они не только такие умные, что нам не стоит беспокоиться о менеджменте памяти. Они еще и очень удобные с точки зрения использования. Мы пользуемся умным указателем почти также, как мы бы пользовались обычным указателем. Все операторы перегружены так, чтобы мы вообще не видели разницу. И мы понимаем, что это обертка, когда нам нужно достать сам сырой указатель и вызвать метод get(). Один из таких случаев - вывод указателя в поток. Если мы захотим вывести на консоль адрес объекта, то нам нужно будет вызывать метод get.
auto u_ptr = std::make_unique<Type>();
std::cout << u_ptr.get() << std::endl;
Но это же странно! Да, это скорее нужно в каких-то отладочно-логировачных целях и ничего более. Но нужно же! Че им стоило перегрузить оператор<<, чтобы мы могли непосредственно сам объект уникального указателя сериализовывать? Видимо больших затрат. Но ребята поднатужились и справились. В С++20 мы можем выводить сам объект без метода get()!
auto u_ptr = std::make_unique<Type>();
std::cout << u_ptr << std::endl;
Ну и конечно в этом случае на консоли появится адрес объекта. Смысл вывода умного указателя на поток всегда был понятен, поэтому хорошо, что комитет включил такое небольшое изменение в стандарт. Теперь уникальный указатель стал еще более тонкой оберткой. Make small changes. Stay cool. #cpp20 #cppcore

​​Вызываем функцию в глобальном скоупе В отличие от С в С++ можно вызывать код до входа в main. В С нельзя вызывать функции в глобальном скоупе, и глобальные переменные там должны быть инициализированы константным выражение. Однако для С++ пришлось ослабить правила. Это было нужно для возможности создания объектов кастомных классов, как глобальных переменных. Для создания объектов нужны конструкторы. А это обычные функции. Поэтому в плюсах можно выполнять пользовательский код до main. Но этот код должен содержаться внутри конструкторов и вызываемых ими функциях. Но просто так вызвать рандомную функция я не могу. Это запрещено. "Ну блин. Мне очень надо. Может как-то договоримся?" Со стандартом не договоришься. Но обходные пути все же можно найти. Например так:
static int dummy = (some_function(), 0);

int main() {}
Здесь мы пользуемся уникальными свойствами оператора запятая: результат первого операнда вычисляется до вычисления второго и после просто отбрасывается. А значение всего выражения задается вторым операндом. Получается, что здесь мы создаем статическую переменную-пустышку, чтобы получить возможность оперировать в глобальном скоупе. Инициализаторы глобальных переменных в С++ могут быть вычисляемыми. Поэтому мы используем свойство оператора запятой, чтобы беспоследственно вычислить some_function, а инициализировать dummy нулем. Вероятнее всего, вам никогда не понадобиться так вызывать функцию. Однако оператор запятая - уникальный инструмент и может выручить даже в таких непростых ситуациях. Have unique tools in your arsenal. Stay cool. #cppcore #goodoldc

Что нужно знать, чтобы попасть в Яндекс, ОЗОН, СБЕР и другой BigTech? Если ты устал от бесконечных задач на LeetCode, то ты н
Что нужно знать, чтобы попасть в Яндекс, ОЗОН, СБЕР и другой BigTech? Если ты устал от бесконечных задач на LeetCode, то ты не один! Ведь LeetCode не даст четкой системы решения задач, а вот ребята из TSKILLS дадут! Приходи на открытый урок "Хакни массивы", чтобы за 1 час бесплатно ты: - Получишь RoadMap, включающие такие темы как Sliding Window, Префиксные массивы... - Решишь 3+ задачи с собеседований в Яндекс, ОЗОН и Сбер - Научишься оценивать задачи в Big-O нотации - Все примеры на С++, а так же (,java, c++, java script, python) Не упусти шанс, выучить алгоритмы НАВСЕГДА за счет четкой системы решения задач на собесе Регистрируйся на открытый урок (ссылку на трансляцию пришлем в ТГ боте после регистрации) Урок пройдет СЕГОДНЯ - 3 ноября (воскресенье) в 12:30

Что нужно знать, чтобы попасть в Яндекс, ОЗОН, СБЕР и другой BigTech? Если ты устал от бесконечных задач на LeetCode, то ты н
Что нужно знать, чтобы попасть в Яндекс, ОЗОН, СБЕР и другой BigTech? Если ты устал от бесконечных задач на LeetCode, то ты не один! Ведь LeetCode не даст четкой системы решения задач, а вот ребята из TSKILLS дадут! Приходи на открытый урок "Хакни массивы", чтобы за 1 час бесплатно ты: - Получишь RoadMap, включающие такие темы как Sliding Window, Префиксные массивы... - Решишь 3+ задачи с собеседований в Яндекс, ОЗОН и Сбер - Научишься оценивать задачи в Big-O нотации - Все примеры на С++, а так же (,java, c++, java script, python) Не упусти шанс, выучить алгоритмы НАВСЕГДА за счет четкой системы решения задач на собесе Регистрируйся на открытый урок (ссылку на трансляцию пришлем в ТГ боте после регистрации) Урок пройдет СЕГОДНЯ - 3 ноября (воскресенье) в 12:30

​​Что происходит до main? Рассмотрим простую программу:
#include <iostream>
#include <random>

int a;
int b;

int main() {
  a = rand();
  b = rand();
  std::cout << (a + b);
}
Все очень просто. Объявляем две глобальные переменные, в main() присваиваем им значения и выводим их сумму на экран. Скомпилировав эту программу, мы сможем посмотреть ее ассемблер и увидеть просто набор меток, соответствующих разным сущностям кода(переменным a и b, функции main). Но вы не увидите какого-то "скрипта". Типа как в питоне. Если питонячий код не оборачивать в функции, то мы точно будем знать, что выполнение будет идти сверху вниз. Так вот, такой простыни ассемблера вы не увидите. Код будет организован так, как будто бы им кто-то будет пользоваться. И это действительно так! Убирая сложные детали, можем увидеть вот такое:
a:
  .zero 4

b:
  .zero 4

main:

  push rbp
  mov rbp, rsp
  call rand
  ...
  call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
  mov eax, 0
  pop rbp
  ret
Суть программы состоит из меток. Метки нужны, чтобы обращаться к сущностям программы. Да, они и внутри основного кода используются. Но то, что на главной функции стоит метка, говорит нам о том, что ее кто-то вызывает! Но даже до того, как начнет работу сущность, которая вызывает main, нужно проделать большую работу по подготовке программы к исполнению. Давайте просто перечислю, что должно быть сделано: 💥 Программа загружается в оперативную память. 💥 Аллокация памяти для стека. Для исполнения функций и хранения локальных переменных обязательно нужен стек. 💥 Аллокация памяти для кучи. Для программы нужна дополнительная память, которую она берет из кучи. 💥 Инициализация регистров. Там их большое множество. Например, нужно установить текущий указатель на вершину стека(stack pointer), указатель на инструкции(instruction pointer) и тд. 💥 Замапить виртуальное адресное пространство процесса. Процессы не работают с железной памятью напрямую. Они делают это через абстракцию, называемую виртуальная память. 💥 Положить на стек аргументы argc, argv(мб envp). Это аргументы для функции main. 💥 Загрузка динамических библиотек. Программа всегда линкуется с разными динамическими либами, даже если вы этого явно не делаете) 💥 Вызов всякий преинициализирующих функций. Важная оговорка, что это все суперсильное упрощение. В реале все намного сложнее. Не претендую на полноту изложения и правильность порядка шагов. К тому же я говорю только про эквайромент полноценных ОС типа окон и пингвина. В эмбеде могут быть сильные отличия. Обязательно оставляйте свои дополнения процесса старта программы в комментариях. В этих полноценных осях всю эту грязную работу на себя берет загрузчик программ. После того, как эти шаги выполнены, загрузчик может вызывать ту самую функцию _start(название условное, зависит от реализации). Она уже выполняет более прикладные чтоли вещи: 👉🏿 Статическая инициализация глобальных переменных. Это и недавно обсуждаемая zero-инициализация и константная инициализация(когда объект инициализирован константным выражением). То есть инициализируется все, что можно было узнать на этапе компиляции. 👉🏿 Динамическая инициализация глобальных объектов. Выполняется код конструкторов глобальных объектов. 👉🏿 Инициализация стандартного ввода-вывода. Об этом мы говорили тут. 👉🏿 Инициализация еще бог знает чего. Начальное состояние рандомайзера, malloc'а и прочего. Так-то это часть первых шагов, но привожу отдельно, чтобы вы не думали, что только ваши глобальные переменные инициализируются. И только вот после этого всего, когда состояние программы приведено в соответствие с ожиданиями стандарта С++, функция _start вызывает main. Так что, чтобы вы смогли выполнить свою программу, кому-то нужно очень мощно поднапрячься... See what's underneath. Stay cool. #OS #compiler

Уже зимой этого года Google, Gmail, Figma и многие другие сервисы могут стать недоступны! К новым санкциям США многие окажутс
Уже зимой этого года Google, Gmail, Figma и многие другие сервисы могут стать недоступны! К новым санкциям США многие окажутся не готовы и рискуют потерять важные данные и связь с миром, но только не те, кто подписан на Telegram канал IT ВЕДОМСТВО Если вы хотите: -> Первыми получать самые актуальные и важные новости из мира технологий... -> Иметь под рукой готовые инструкции по жизни в техно мире, включая полный список рабочих аналогов популярных программ... -> Знать, как защитить от мошенников в сети свои данные и деньги... ... то не теряйте времени, подписывайтесь на Telegram канал IT ВЕДОМСТВО и будьте готовы к любым изменениям!

Ответ Самый главный результат опроса - почти треть канала состоит из красивых людей. In mom's humble opinion. И это прекрасно! И настоящие профессионалы, и внешне обаятельны, и в душе поэты! Но ладно, это была лирика(поэт из меня так себе). Перейдем к правильному ответу. Он был замаскирован, видимо поэтому набрал меньше всего голосов. Стандарт говорит:
Because the explicit template argument list follows the function template name, 
and because constructor templates are named without using a function name, 
there is no way to provide an explicit template argument list for these function templates.
Невозможно явно указать шаблонные аргументы для шаблонного конструктора. Компилятор должен суметь вывести эти типы на основе переданных в конструктор аргументов. Но так как в нашем случае конструктор не принимает никаких параметров - компилятор никак не сможет вывести типы. Поэтому невозможно вызвать конструктор у такого класса:
struct Type {
    template <typename>
    Type() {}
};
Но! Объект такого класса создать можно. Некоторые функции в С++ неявно создают объекты. Например std::bit_cast.
struct Type {
  template <typename>
  Type() {};
};

struct Other {};

int main() {
  Type t = std::bit_cast<Type>(Other{});
}
Спасибо @cppnyasha за пример. Но конкретно в нашем случае была задача вызвать конструктор, а это невозможно. Solve problems. Stay cool. #cppcore #cpp20

Как правильно вызвать конструктор у такого класса?
Anonymous voting

Квиз Вчера в комментах наш подписчик @d7d1cd задал очень интересную задачку, которой мне захотелось с вами поделиться. Да, кто-то уже ее обсудил, но тем, кто не участвовал в дискуссии, тоже будет интересно проверить свои знания в #quiz. Как всегда это бывает с плюсами, задачка не простая и сходу вгоняет в ступор. Но без паники! Вдох, выдох и мы опять играем в любимых, успокоили разум, подумали и ответили. Подписчику спасибо за контент, а у меня для вас всего один вопрос. Как правильно вызвать конструктор у такого класса?
struct Type {
    template <typename>
    Type() {}
};
Challenge your knowledge. Stay cool.

14 ноября, YADRO С++ meetup, Москва и онлайн Встречаемся очно или на трансляции — регистрируйтесь, чтобы забронировать место
14 ноября, YADRO С++ meetup, Москва и онлайн Встречаемся очно или на трансляции — регистрируйтесь, чтобы забронировать место и получить ссылку на стрим на одной из популярных платформ.  В программе: • Как сочетать объектный подход с современным программированием. • Паттерны ООП, которые сделают проект гибким и легким в поддержке. • Удобство интрузивных контейнеров. • Что помогает нам в разработке 5G сетей. • Инструменты библиотеки Boost.Intrusive. • Дискуссия: эксперты из YADRO, Яндекса, Syntacore и Касперского обсудят, как протекает реализация стандартов: на сколько быстро внедряются фичи, стоит ли обратить внимание на подходы других языков и становится ли продукт безопасней, если написан на новых стандартах? Офлайн-участники смогут увидеть «железо» для ЦОД и телеком-операторов, код для которого пишут С++ разработчики YADRO, пообщаться с экспертами на мини-стендах, поучаствовать в технических интерактивах и получить призы после митапа. Участие бесплатное, регистрация обязательна. До встречи!

А за сколько вы выучили С++?
А за сколько вы выучили С++?

​​Линкуем массивы к объектам Опытные читатели могли заметить кое-что странное в этом посте. И заметили кстати. Изначально cin, cout и тд определены, как простые массивы. А в iostream они уже становятся объектами потоков и линкуются как онные. То есть в одной единице трансляции
extern std::ostream cout;
extern std::istream cin;
...
А в другой
 // Standard stream objects.
  // NB: Iff <iostream> is included, these definitions become wonky.
  typedef char fake_istream[sizeof(istream)]
  attribute ((aligned(alignof(istream))));
  typedef char fake_ostream[sizeof(ostream)]
  attribute ((aligned(alignof(ostream))));
  fake_istream cin;
  fake_ostream cout;
  fake_ostream cerr;
  fake_ostream clog;
Что за приколы такие? Почему массивы нормально линкуются на объекты кастомных классов? В С++ кстати запрещены такие фокусы. Типы объявления и определения сущности должны совпадать. Все потому что линкер особо не заботится о типах, выравнивании и даже особо о размерах объектов. То есть я буквально могу прилинковать объект одного кастомного класса к другому и мне никто никакого предупреждения не влепит. Такой код вполне нормально компилится и запускается:
// header.hpp
#pragma once

struct TwoFields {
  int a;
  int b;
};

struct ThreeFields {
  char a;
  int b;
  long long c;
};

// source.cpp

ThreeFields test = {1, 2, 3};

// main.cpp

#include <iostream>
#include "header.hpp"

extern TwoFields test;

int main() {
  std::cout << test.a << " " << test.b << std::endl;
}
На консоли появится "1 2". Но ни типы, ни размеры типов, ни выравнивания у объектов из объявления и определения не совпадают. Поэтому здесь явное UB. Но в исходниках GCC так удачно сложилось, что массивы реально представляют собой идеальные сосуды для объектов io-потоков. На них даже сконструировали реальные объекты. Поэтому такие массивы можно интерпретировать как сами объекты. Это, естественно, все непереносимо. Но поговорка "спички детям - не игрушка" подходит только для тех, кто плохо понимает, что делает. А разработчики компилятора явно не из этих ребят. Take conscious risks. Stay cool. #cppcore #compiler

Я айтишник и я устал! С годами работы в IT все сильнее напрягает рутина, прокрастинация, куча задач и 0 желания их выполнять.
Я айтишник и я устал! С годами работы в IT все сильнее напрягает рутина, прокрастинация, куча задач и 0 желания их выполнять. Че делать? Хватит грызть самого себя и заставлять через силу - сделаешь только хуже! Лучше подпишись на того, кто уже не первый год работает с IT-специалистами и помогает им справиться с апатией и прокрастинацией - Психолог с научным подходом. ✔️ Как оторваться от ленты соцсетей и сесть за работу с удовольствием? ✔️ Как спокойно общаться с коллегами, если они бесят? ✔️ Как избавиться от постоянной тревожности? ✔️ Как успокоить конфликты в семье и перестать срываться на всех, а вместо этого получить поддержку и понимание со стороны близких? Подписывайся на канал @remizov_changes - начни работать и жить в кайф, не скатываясь в кризисы и выгорание! А в закрепе тебя уже ждут бонусы: 👨🏻‍💻 Видео, в котором ты найдёшь ответ на вопрос «Почему у тебя нет энергии и что с этим делать» + гайд как it-специалисту вернуть энергию, даже если не получается отдохнуть. https://t.me/+srQ8N42LB-42ODFi

​​std::cout Кажется, что на начальном этапе становления про-с++-ером, вывод в использование конструкции:
std::cout << "Print something in consol\n";
воспринимается, как "штука, которая выводит текст на консоль". Даже со временем картинка не до конца складывается и на вопрос "что такое std::cout?", многие плывут. Сегодня закроем этот вопрос. В этой строчке мы вызываем такой оператор:
std::ostream& operator<< (std::ostream& stream, const char * str)
Получается, что std::cout - объект класса std::ostream. И ни какой-то там временный. Раз он принимается по левой ссылке, значит он уже где-то хранится в памяти. Но мы же ничего не делаем для его создания? Откуда он взялся? Мы говорили о том, что есть "невидимые" для нас вещи, которые происходят при старте программы. Так вот, это одна из таких вещей. std::cout - глобальный объект типа std::ostream. За его создание отвечает класс std::ios_base::Init, инстанс которого явно или неявно определяется в библиотеке <iostream>. Но это все слова. И новичкам будет достаточно этого. Но мы тут глубоко закапываемся, поэтому давайте закопаемся в код. Полазаем по исходникам gcc. Ссылочки кликабельные для пытливых умов. А в хэдэре iostream мы можем найти вот это:
extern istream cin;  ///< Linked to standard input
extern ostream cout;  ///< Linked to standard output
extern ostream cerr;  ///< Linked to standard error (unbuffered)
extern ostream clog;  ///< Linked to standard error (buffered)
...
static ios_base::Init __ioinit;
Здесь определяются символы стандартных потоков и создается глобальная переменная класса ios_base::Init. Пойдемте тогда в конструктор:
ios_base::Init::Init()
  {
    if (__gnu_cxx::__exchange_and_add_dispatch(&_S_refcount, 1) == 0)
      {
    // Standard streams default to synced with "C" operations.
    _S_synced_with_stdio = true;

    new (&buf_cout_sync) stdio_sync_filebuf<char>(stdout);
    new (&buf_cin_sync) stdio_sync_filebuf<char>(stdin);
    new (&buf_cerr_sync) stdio_sync_filebuf<char>(stderr);

    // The standard streams are constructed once only and never
    // destroyed.
    new (&cout) ostream(&buf_cout_sync);
    new (&cin) istream(&buf_cin_sync);
    new (&cerr) ostream(&buf_cerr_sync);
    new (&clog) ostream(&buf_cerr_sync);
    cin.tie(&cout);
    cerr.setf(ios_base::unitbuf);
    // _GLIBCXX_RESOLVE_LIB_DEFECTS
    // 455. cerr::tie() and wcerr::tie() are overspecified.
    cerr.tie(&cout);
    ...
    __gnu_cxx::__atomic_add_dispatch(&_S_refcount, 1);
Немножко разберем происходящее. В условии проверяется ref_count, чтобы предотвратить повторную инициализацию. Так как не предполагается, что такие объекты, как cout будут удалены, они просто создаются через placement new с помощью инстансов stdio_sync_filebuf<char>. Это внутренний буфер для объектов потоков, который ассоциирован с "файлами" stdout, stdin, stderr. Буферы как раз и предназначены для получения/записи io данных. Хорошо. Мы видим как и где создаются объекты. Но это же placement new. Для объектов уже должная быть подготовлена память для их размещения. Где же она? В файлике globals_io.cc:
 // Standard stream objects.
  // NB: Iff <iostream> is included, these definitions become wonky.
  typedef char fake_istream[sizeof(istream)]
  attribute ((aligned(alignof(istream))));
  typedef char fake_ostream[sizeof(ostream)]
  attribute ((aligned(alignof(ostream))));
  fake_istream cin;
  fake_ostream cout;
  fake_ostream cerr;
  fake_ostream clog;
то есть, объекты - это пустые символьные массивы правильного размера и выравнивания. Все это должно вам дать довольно полное представление, что такое стандартные потоки ввода-вывода. #cppcore #compiler