Маша С++
104 subscribers
54 photos
2 files
65 links
Учу C++ и не только вместе с вами, мои котятки)
Download Telegram
Посмотрите на этот код:
%:include <iostream>

struct X
<%
compl X() <%%> // destructor
X() <%%>
X(const X bitand) = delete; // copy constructor
// X(X and) = delete; // move constructor

bool operator not_eq(const X bitand other)
<%
return this not_eq bitand other;
%>
%>;

int main(int argc, char* argv<::>)
<%
// lambda with reference-capture:
auto greet = <:bitand:>(const char* name)
<%
std::cout << "Hello " << name << " from " << argv<:0:> << '\n';
%>;

if (argc > 1 and argv<:1:> not_eq nullptr)
greet(argv<:1:>);
else
greet("Anon");
%>


На первый взгляд может показаться, что это какой-то всевдо-C++ или исходник для некоего препроцессора. Но на моем окружении данный код скомпилился и отработал как ожидалось, также как и например тут и здесь.

Что это такое? Альтернативные токены.
👍4
Читаю тут статью ~20-летней давности. Улыбнуло, что БД размером в 9Гб в банке (!) на тот момент оказалась проблемой. Всё-таки всё очень сильно изменилось с тех пор (привет, электрон), а Итаниум давно почил в бозе.

В качестве примера можно привести интеграцию Альфа-Банком в свою IT-инфраструктуру платформы на базе Itanium 2. Рост инвестиционного бизнеса банка привел к тому, что система в имеющейся конфигурации перестала справляться с возрастающей нагрузкой: задержки в обслуживании пользователей временами достигали критической отметки. Анализ ситуации показал, что узким местом системы является не производительность процессоров, а ограничение 32-разрядной архитектуры в части подсистемы памяти, не позволяющей эффективно использовать больше 4 Гбайт адресного пространства сервера. Объем базы данных превышал 9 Гбайт. Использовалась она весьма интенсивно, что приводило к критической загрузке подсистемы ввода-вывода. Альфа-Банк принял решение о закупке кластера из двух четырехпроцессорных серверов на базе Itanium 2 с объемом оперативной памяти — 12 Гбайт.
3👍2
#база
Какой из обработчиков catch сработает в таком коде?
#include <iostream>

class BaseException : public std::exception {};
class DerivedException : public BaseException {};

int main(int argc, char* argv<::>)
{
try
{
throw DerivedException();
}
catch (const std::exception&)
{
std::cout << "std::exception" << std::endl;
}
catch (const BaseException&)
{
std::cout << "BaseException" << std::endl;
}
catch (const DerivedException&)
{
std::cout << "DerivedException" << std::endl;
}

return 0;
}

Не смотря на то, что блок с DerivedException выглядит наиболее подходящим, выберется первый блок catch, который сможет обработать исключение, то есть std::exception, потому что он является родителем DerivedException.
Это значит, что наиболее специализированные исключения нужно располагать первыми, то есть порядок в коде должен быть обратным.
👍41
Нам всегда говорили, что бросать исключения из деструктора это плохая идея. Один из примеров плохого результата - это раскручивание стека при обработке исключения, когда вызываемые деструкторы приводят к повторному исключению, которое кроме как вызовом terminate() толком не обработаешь. Но что, если выбросить исключение очень хочется, ведь мы сделаем всё "аккуратно"? Давайте посмотрим на такой код:
#include <iostream>

struct ThrowsInDtor
{
~ThrowsInDtor()
{
std::cout << "~ThrowsInDtor" << std::endl;
throw std::runtime_error("holy...");
}
};

int main(int argc, char* argv<::>)
{
try
{
ThrowsInDtor dtor;
}
catch (const std::runtime_error& ex)
{
std::cout << "ex: " << ex.what() << std::endl;
}

return 0;
}


Что тут может пойти не так, вроде же всё ОК? Одно исключение, мы его ловим. По факту, данный код приведет к вызову terminate(), так как все деструкторы по-умолчанию неявно помечены как noexcept, то есть не выбрасывающие исключений.

Если очень сильно хочется вернуться во времена до C++11, то помечаем деструктор как noexcept(false) и получаем наше исключение (не понятно только зачем):
~ThrowsInDtor() noexcept(false)
👍5
Объединения (union-ы) позволяют хранить данные разных типов в одной области памяти. Пример:
#include <iostream>

union IPv4Address
{
uint32_t i;
uint8_t c[4];
};

int main(int argc, char* argv<::>)
{
IPv4Address a;
memset(&a, 0, sizeof(a));
a.c[0] = 10;
a.c[1] = 0;
a.c[2] = 0;
a.c[3] = 1;

std::cout << (int)a.c[0] << std::endl;
std::cout << a.i << std::endl; // don't do this, just an example

return 0;
}


Но можно ли наследовать union? Давайте попробуем:
struct IPv4AddressWithPort : IPv4Address
{
uint16_t port = 0;
};


Получим ошибку компиляции: enum/union cannot be used as a base class

Объединения нельзя использовать как базовый класс. Напишите в комментариях, кто знает почему)
👍3
Дочитываю книгу "Многопользовательские игры. Разработка сетевых приложений". Неплохое введение в компьютерные сети для тех кто хочет быстро разобраться или вспомнить как работает IP, Nat и тп, а также вводная глава про программирование сетевых сокетов. Примеры на C++.
#книги
👍3🔥3
Многим известно, что std::string можно создать из указателя на char * (допустим из некой C-шной функции):
#include <iostream>
#include <string>

static std::string str = "OK";

const char * getStr() {
return str.c_str();
}

int main() {
std::cout << "str: " << std::string(getStr()) << std::endl;
}


Но что будет, если нам прилетит nullptr и мы передадим его в конструктор std::string?
const char* getStr() {
return nullptr;
//return str.c_str();
}


Можно подумать, что создастся пустая строка. В конце концов, почему бы и нет, я бы например реализовала строку именно так. На самом же деле передавать nullptr в конструктор строки нельзя и в общем случае будет UB.

На gcc у меня это приводит к std::logic_error. А вот на msvc уже будет Access violation reading location 0x0000000000000000.

Ситуация немного прояснилась с выходом C++23. Создавать строку из nullptr более нельзя:
basic_string( std::nullptr_t ) = delete; // C++23
std::string s = nullptr; // нельзя

Хотя кажется, что проблему с возвратом nullptr это не решает.
👍53🔥2
Как можно оформить в C++ длинный строковый литерал и разбить его на нескольксо строк в коде?

Допустим, мы хотим разбить длинную строку в коде на несколько линий, но так чтобы переносы строк и отступы из самого кода не повлияли на результат.

Один из проcтейших способов - просто написать строки в кавычках друг за другом:
std::string str1 =
"Если ты, следуя правому разуму, будешь старательно,"
" ревностно и любовно относиться к делу, которым ты в"
" данный момент занят...";
std::cout << str1 << std::endl; // напечатает всё в одну строку


Такой вариант склеит строки без переносов \n, но тут важно самостоятельно не пропустить пробелы в местах разрывов.

Однако, есть и другой способ: можно использовать кавычки только в начале и конце всей строки, вставив символ \
как самый последний (!) на каждой строке:
std::string str1 =
"Если ты, следуя правому разуму, будешь старательно, \
ревностно и любовно относиться к делу, которым ты в \
данный момент занят...";
std::cout << str1 << std::endl; // напечатает всё в одну строку


При этом начинать новую строку следует с самого начала, иначе любой отступ в коде
попадет и в саму строку. Неудобно, правда?

А как же raw string literals? Те самые, что заключаются в R"( и )" (или другие разделители)...
Да, они замечательно подходят, чтоб не экранировать разные символы, вроде кавычек и обратных слешей:
std::string json = R"(
{
"path": "C:\Program Files\",
}
)";


Но что будет, если взять нашу цитату, перенесенную по строкам в коде:
    std::string str1 =
R"(Если ты, следуя правому разуму, будешь старательно,
ревностно и любовно относиться к делу, которым ты в
данный момент занят...)";


Оно вставит переносы строк \n и отступы из кода в результат.

Мой победитель: просто строки в двойных кавычках на каждой строке. Просто, привычно, не вставляет ненужных символов и позволяет форматировать код.
👍62🔥2
Многим известно как работает dynamic_cast в C++. Зная, что за указателем на базовый класс стоит объект производного, мы можем скастовать базовый до производного и вызвать его метод. Пример:
struct Base {
virtual ~Base() = default;
};
struct Derived : public Base {
void doDerivedJob() {
std::cout << "derived" << std::endl;
}
};

void cast(Base* baseP) {
Derived* d = dynamic_cast<Derived*>(baseP);
if (d) {
d->doDerivedJob();
} else {
std::cout << "ptr: unable to cast" << std::endl;
}
}

int main(int argc, char* argv<::>)
{
Base b;
Derived d;

cast(&b); // ptr: unable to cast
cast(&d); // derived

return 0;
}

Все, что нужно для корректной работы dynamic_cast - это наличие хотя бы одного виртуального метода. И тогда мы можем в рантайме проверить результат кастования на nullptr. Если это nullptr - то мы не угадали и указатель смотрел на объект базового класса.
Обычно такой подход рассматривается как результат неудачного проектирования, но иногда все же приходится так делать. К тому же проверка указателя на nullptr довольно обычная операция.

Но что делать, если мы имеем не указатель, а ссылку на базовый класс? Оказывается, что dynamic_cast работает как ожидается и со ссылками - можно преобразовать ссылку на базовый класс в ссылку на производный. Однако, что делать, если преобразование в рантайме невозможно? Ссылка, в отличие от указателя не может указывать на nullptr. В случае ошибки преобразования dynamic_cast выбросит исключение std::bad_cast. Пример:
void cast(Base & baseRef) {
try {
Derived& d = dynamic_cast<Derived&>(baseRef);
d.doDerivedJob();
} catch (const std::bad_cast& ex) {
std::cout << "ref: unable to cast: " << ex.what() << std::endl;
// baseRef.doWork();
}
}


int main(int argc, char* argv<::>)
{
Base b;
Derived d;

cast(b); // ref: unable to cast: Bad dynamic_cast
cast(d); // derived

return 0;
}

Однако, этот пример показывает плохой стиль программирования, потому что использует исключения для потока выполнения.
4
Как подсчитать число выставленных битов (единиц) в целом беззнаковом?
Давайте возьмем C++ 20, где в <bit> появилась функция std::popcount, которая делает, то что нам нужно:
#include <iostream>
#include <bit>
#include <cstdint>

int main() {

uint8_t u1 = 0b00011101;
unsigned u2 = 123; // 0b1111011

std::cout << std::popcount(u1) << " " <<
std::popcount(u2) << std::endl; // 4 6

return 0;
}


Скучно? На первый взгляд да, но в C++ всегда можно закопаться в детали =)
Во-первых, что за странное название? Почему не какой-нибудь bit_set_count?
Оказывается, что popcount это сокращение от “population count”.
Вроде немного понятнее, но лучше не стало)

Чтож, почитаем хабр: https://habr.com/ru/articles/467083/
Кроме пространного списка задач, где для решения используется эта функция, из статьи мы узнаём
что во многих процессорах есть специальная инструкция (н-р POPCNT на X86), которая выполняет
данное вычисление очень быстро (за такт?).

Кроме того, в GCC и CLANG есть встроенные функции, которые также вычисляют popcount очень быстро и что-то мне подсказывает, что они могут вызывать эту единственную инструкцию (а может и нет).

В GCC эта функция наз-ся __builtin_popcount. Пример можно посмотреть тут: https://godbolt.org/z/84ETrhfnK
Как видим, в дебаге GCC вставит встроенную __popcountdi2 функцию, а для std::popcount будет другая.
Если же добавить -O3, то разницы не будет никакой.
Замеры делать не буду)
👍51
Давайте посмотрим на такой пример заголовочного файла (.h) c использованием указателя на incomplete тип (forward declaration):

#include <memory>

class ForwardDClass;

class WithSmartPtr
{
public:
WithSmartPtr() = default;
~WithSmartPtr() = default;
private:
ForwardDClass* rawPtr_;
//std::shared_ptr<ForwardDClass> sPtr_;
//std::unique_ptr<ForwardDClass> uPtr_;
};

int main(int argc, char* argv<::>)
{
WithSmartPtr w;

return 0;
}

Не будем вдаваться в корректность, посмотрим для начала на компилируемость.
Такой код, конечно же соберется, ведь компилятор знает размер указателя в байтах (размер одинаков как для определенного, так и неопределенного типа).

Если мы раскомментируем строку с shared_ptr, то код тоже скомпилируется (по крайней мере у меня собрался). Не будем говорить, про корректность его работы, но напомним, что вцелом удаление неопределенного типа - UB.
А вот если мы раскомментируем строку с unique_ptr, то код перестанет собираться.
Многие используют паттерн pimpl и потому знают, что реализацию деструктора нужно сделать в других исходниках (например .cpp), где forward declaration тип определен и проблема этим решается.
Но почему компилятор не ругается на shared_ptr?
Дело в том, что unique_ptr и shared_ptr требуют полного определения типа в разных местах. Детали сложны (и о них я не знаю), но первому нужен статический deleter, тогда как второму - динамический.
Вывод: в нашей копилке костылей еще одно неправильное решение - заменить unique_ptr на shared_ptr 😊.
👍31
В честь наступающего Нового Года запощщу тупой мем 🤩🤩
@jokespp
Please open Telegram to view this post
VIEW IN TELEGRAM
🎄31
По умолчанию, при форматировании вывода в поток булевых значений, будет использоваться
1 и 0 для true и false. Пример:

bool t = true;
bool f = false;
std::cout << t << " " << f << std::endl;
// Выведет: 1 0

Возможно, что удобнее (особенно для отладки) выводить булевы значения строкой,
также как они задаются в коде. Для этого нужно добавить в поток манипулятор boolalpha:

std::cout << std::boolalpha;
std::cout << t << " " << f << std::endl;
// Выведет: true false


Интересно то, что манипулятор действует на все последующие записи булевых значений
для этого потока. То есть он будет применяться и после ; и после std::endl.
Отменить форматирование булевых флагов можно записав в поток манипулятор
std::noboolalpha:

std::cout << std::noboolalpha << t << " " << f << std::endl; // 1 0


(Интересно, а для ввода это работает? кто проверит?)

Честно говоря, мне бы хотелось увидеть нормальные true/false по-умолчанию,
но видимо обратная совместимость.

Еще одной интересной особенностью форматирования вывода является округление
значений с плавающей точкой.
Посмотрим на вывод такого примера (на вашем окружении может быть другой результат):

double d1 = 0.1234567890;
double d2 = 0.1234564890;
std::cout << d1 << std::endl;
std::cout << d2 << std::endl;
// Вывод:
// 0.123457
// 0.123456

Поток выбрал точность в 6 знаков после точки (мы эту точность не задавали, поэтому может быть другой),
и проведет не отсечение, а округление в последней цифре.
👍3
#Шпаргалка-повторялка. Rvalue или Lvalue?

Упрощенно Lvalue (left value) это то, что стоит слева от оператора присваивания =, а rvalue (right value) справа:
lvalue = rvalue. Однако, все немного сложнее.

Если можно получить адрес некоего выражения, то зачастую это lvalue. Если нельзя - то это обычно rvalue.

Rvalue концептуально часто временный объект без имени (например результат работы функции, временное значение, литерал).
А вот lvalue обычно имеет имя.

А еще rvalue могут быть перемещены, но об этом в другой раз.
👍3🔥2
Использует ли std::optional указатели для реализации?

Нет. std::optional не выделяет память в куче, он может полностью работать на стеке.

Объект создается на месте в стековом куске памяти (размером с объект) внутри опшинала.
Поэтому нет и накладных расходов на переход по указателю (но есть накладные расходы на bool флаг для состояния инициализации + выравнивание).

Однако возникает вопрос, расходует ли std::optional память под неинициализированный объект? Ведь хранилище выделятся всегда на стеке. Думаю, что вы можете ответить на него самостоятельно (а еще посмотреть на sizeof для жирного объекта).

Выходит, что std::optional и не такой уж optional, память то всегда жрет)

Кстати, по причине такой реализации optional нельзя создавать с некоторыми типами, например не пойдут cсылки, функции, void, массивы.
👍21🔥1
Интересна ли тема Qt?

Сегодня речь пойдет про QPointer. Как известно, в Qt всё строится вокруг QObject со своей механикой владения объектами, отличной от использования "стандартных" умных указателей, типа std::shared_ptr.

Давайте посмотрим на такой пример: в наш класс передается некий наследник QObject, которым мы не владеем. Далее в какой-то момент времени нам необходимо удостовериться, а существует ли этот объект еще или нет.

struct Class: {
Class(QObject * obj) : obj_(obj) {}

void do() {
if (obj_) { // жив ли объект по указателю?
//obj_->method(); // UB, если нет
};
}

QObject * obj_;
};


В стандартном C++ мы бы воспользовались weak_ptr. Но в Qt есть специальный класс с довольно абстрактным названием QPointer, который ведет себя как обычный сырой указатель, однако автоматически очищается когда хранимый им указатель удаляется. Поэтому мы можем проверить, удалился ли объект по указателю.

Внесем простые изменения в класс:
QPointer<QObject> * obj_;
Теперь проверка if (obj_) будет работать как ожидается, при этом нам не обязательно оборачивать указатель в QPointer при передаче в конструктор класса.

В чем отличия от weak/shared ptr? Во-первых не нужно изначально оборачивать указатель в shared, что может быть не всегда и возможно. Во-вторых работает только с наследниками QObject. Ну и механизм работы отличается, что кстати может легко сломаться в многопоточном коде.

О мёртвых указателях либо хорошо, либо ничего.
👍5😁2🔥1
#мем что ли выложить
Идет корова по лесу, видит, проект горит. Села в нее и выгорела.
😁7
Начиная с С++23 официально доступны директивы #elifdef и #elifndef, так что теперь например вместо:

#ifdef WINDOWS
//
#elif defined(MACOS)
//
#elif defined(LINUX)
//
#endif

можно будет писать короче и консистентнее:

#ifdef WINDOWS
//
#elifdef MACOS
//
#elifdef LINUX
//
#endif

Краткость — сестра таланта (и брат нового стандарта)
👍51