Маша С++
104 subscribers
54 photos
2 files
65 links
Учу C++ и не только вместе с вами, мои котятки)
Download Telegram
Как можно оформить в 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
Утверждён стандарт C++26.
Мне понравились новые няшные операторы ^^ 😇:
static_assert(^^i != ^^j);
https://www.opennet.ru/opennews/art.shtml?num=65102
Please open Telegram to view this post
VIEW IN TELEGRAM
😁5👍1
Чистовик: Сайт отслеживает переход популярных проектов на модули C++. По прогнозу к 2167 году переедут все 😛:
https://arewemodulesyet.org/
Please open Telegram to view this post
VIEW IN TELEGRAM
4😁3🔥1