std::atexit регистрирует функцию для вызова при "нормальном" завершении программы (например выходе из main или вызова std::exit).Если программа завершается аварийно, например из-за
terminate(), то вызов не срабатывает. Давайте напишем программу, которая выведет в лог текст, если не "крэшнулась". Крэш сэмулируем с вероятностью 50% с помощью исключения:#include <cstdlib>
#include <iostream>
#include <time.h>
void atexit_handler()
{
std::cout << "Hooray, not crashed" << std::endl;
}
int main()
{
std::atexit(atexit_handler);
srand(time(NULL));
if (rand() % 2 == 0) {
throw std::runtime_error("Let's crash");
}
std::cout << "app end" << std::endl;
return 0;
}
В случае нормального завершения вывод будет:
app end
Hooray, not crashed
а при исключении - ни одна из строк.
При этом обработчиков-функций может быть несколько (как минимум 32).
👍7
В продолжении темы про аварийное завершение работы. Что делать, если статическая или thread_local переменная в конструкторе выбрасывает исключение? Ничего другого, кроме как вызвать std::terminate нет.
Следует отметить, что это верно и для исключения в деструкторе.
#include <iostream>
class ThrowsClass
{
public:
ThrowsClass()
{
throw std::runtime_error("oh shit");
}
~ThrowsClass()
{
throw std::runtime_error("dtor");
}
void print()
{
std::cout << "throws";
}
};
static ThrowsClass t; // std::terminate
thread_local ThrowsClass tl; // std::terminate
int main() {
tl.print();
std::cout << "end";
}
Следует отметить, что это верно и для исключения в деструкторе.
👍4
Рассмотрим шаблон функции, который задает не только типы аргументов, но и тип результата, например дробное или целочисленное деление:
Мы могли бы вызвать этот код так:
Код скомпилируется и верно отработает, выдав заказанное нами дробное значение. Типы int для аргументов он выведет автоматом и нам их не нужно писать в угловых скобках (double написать все же придется).
Ну и что такого, спросите вы, это же ожидаемо? А давайте посмотрим, что было бы, если бы мы написали эту же функцию, но с другим порядком типов:
Этот код не скомпилируется и нам пришлось бы писать все 3 параметра, чтоб уважить компилятор:
Как вы понимаете, порядок важен и пропускать типы можно только для крайних справа параметров.
#cpp #templates
template <typename T1, typename T2, typename T3>
T1 div(T2 a, T3 b) {
return (T1)a / b;
}
Мы могли бы вызвать этот код так:
int main()
{
auto res = div<double>(3, 2);
std::cout << res << std::endl; // 1.5
return 0;
}
Код скомпилируется и верно отработает, выдав заказанное нами дробное значение. Типы int для аргументов он выведет автоматом и нам их не нужно писать в угловых скобках (double написать все же придется).
Ну и что такого, спросите вы, это же ожидаемо? А давайте посмотрим, что было бы, если бы мы написали эту же функцию, но с другим порядком типов:
template <typename T1, typename T2, typename T3>
T3 div_v2(T1 a, T2 b) {
return (T3)a / b;
}
auto res2 = div_v2<int>(3, 2); // won't compile!
Этот код не скомпилируется и нам пришлось бы писать все 3 параметра, чтоб уважить компилятор:
auto res2 = div_v2<int, int, int>(3, 2); // OK
Как вы понимаете, порядок важен и пропускать типы можно только для крайних справа параметров.
#cpp #templates
👍4
Всем известно, что язык C++ является прямым потомком языка C и позаимствовал из него очень многое. Но работает ли это в обратную сторону? Судите сами. Изменения в стандарте C23 (не С++23!) среди прочего включают:
- изменения в auto для:
- поддержка синтаксиса [[attr]], например:
- добавлены ключевые слова
- добавлены nullptr_t и nullptr
- добавлен разделитель ':
- поддержка constexpr
Подробнее тут: https://www.opennet.ru/opennews/art.shtml?num=62278
- изменения в auto для:
auto y = cos(x);- поддержка синтаксиса [[attr]], например:
[[deprecated]]- добавлены ключевые слова
bool, static_assert и другие- добавлены nullptr_t и nullptr
- добавлен разделитель ':
1'000'000- поддержка constexpr
Подробнее тут: https://www.opennet.ru/opennews/art.shtml?num=62278
www.opennet.ru
GCC 15 будет использовать стандарт C23 по умолчанию
В кодовую базу, на основе которой формируется запланированный на весну следующего года выпуск набора компиляторов GCC 15, принято изменение, включающее по умолчанию использование стандарта С23 с расширениями GNU ("-std=gnu23") при компиляции программ на языке…
👍6
Как быстро и просто инвертировать вектор? Передадим reverse-итераторы в конструктор вектора, получим нужную копию:
Коротко, но может быть не самый эффективный вариант? Что ж, сравним с такой мамкиной реализацией:
После нескольких замеров оказалось, что более короткая реализация при N = 10М выполняется за 12 мс, а более длинная за 19мс. Уотак вот, можно пользоваться и радоваться мощи итераторов.
std::vector<int> reverse(const std::vector<int>& source) {
return std::vector(source.crbegin(), source.crend());
}Коротко, но может быть не самый эффективный вариант? Что ж, сравним с такой мамкиной реализацией:
std::vector<int> reverse2(const std::vector<int>& source) {
std::vector<int> res;
res.reserve(source.size());
for (auto it = source.crbegin(); it != source.crend(); ++it) {
res.push_back(*it);
}
return res;
}После нескольких замеров оказалось, что более короткая реализация при N = 10М выполняется за 12 мс, а более длинная за 19мс. Уотак вот, можно пользоваться и радоваться мощи итераторов.
👍3🔥2
Во тут народ извращается 🤣
https://t.me/grokaemcpp/577
Но что, если я вам скажу, что вы можете наследоваться от лямбды!
https://t.me/grokaemcpp/577
Please open Telegram to view this post
VIEW IN TELEGRAM
Telegram
Грокаем C++
Наследование? От лямбды? Ч2
#опытным
Лямбды - это функциональные объекты по своей сути. Объекты классов с перегруженным оператором(). Зачем вообще от такой сущности наследоваться? Можно же просто сделать обычный класс с такими же перегруженным оператором…
#опытным
Лямбды - это функциональные объекты по своей сути. Объекты классов с перегруженным оператором(). Зачем вообще от такой сущности наследоваться? Можно же просто сделать обычный класс с такими же перегруженным оператором…
😁6❤2🤯1
Проходили тут курс по python, и там конечно же сказано, что в python отступы в блоках обязательны, а во многих других языках - нет. И мне даже показалось, что это в недостатки других языков внесли. Типа вот глядите какую нечитаемую фигню можно на C написать, а на python всё замечательно. И приводят такой пример (кстати, рекомендую запустить код):
P.S. на телефоне разметка может разъехаться.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <unistd.h>
k;double sin()
,cos();main(){float A=
0,B=0,i,j,z[1760];char b[
1760];printf("\x1b[2J");for(;;
){memset(b,32,1760);memset(z,0,7040)
;for(j=0;6.28>j;j+=0.07)for(i=0;6.28
>i;i+=0.02){float c=sin(i),d=cos(j),e=
sin(A),f=sin(j),g=cos(A),h=d+2,D=1/(c*
h*e+f*g+5),l=cos (i),m=cos(B),n=
sin(B),t=c*h*g-f* e;int x=40+30*D*
(l*h*m-t*n),y= 12+15*D*(l*h*n
+t*m),o=x+80*y, N=8*((f*e-c*d*g
)*m-c*d*e-f*g-l *d*n);if(22>y&&
y>0&&x>0&&80>x&&D>z[o]){z[o]=D;;;b[o]=
".,-~:;=!*#$@"[N>0?N:0];}}/*#****!!-*/
printf("\x1b[H");for(k=0;1761>k;k++)
putchar(k%80?b[k]:10);A+=0.04;B+=
0.02;}}/*****####*******!!=;:~
~::==!!!**********!!!==::-
.,~~;;;========;;;:~-.
..,--------,*/
P.S. на телефоне разметка может разъехаться.
😁9❤1
Наверное, все знают про перечисления с ограничением области видимости
А кто из вас знал, что можно использовать
enum class:enum class Color {
Red,
Green,
Blue
};А кто из вас знал, что можно использовать
enum struct ? (👍🏻👎🏻)👍7❤2
Посмотрите на этот код:
На первый взгляд может показаться, что это какой-то всевдо-C++ или исходник для некоего препроцессора. Но на моем окружении данный код скомпилился и отработал как ожидалось, также как и например тут и здесь.
Что это такое? Альтернативные токены.
%: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 Гбайт.
PVS-Studio
Оптимизация 64-битных программ
В статье рассмотрен ряд способов повышения производительности 64-битных Windows приложений.
❤3👍2
#база
Какой из обработчиков catch сработает в таком коде?
Не смотря на то, что блок с DerivedException выглядит наиболее подходящим, выберется первый блок catch, который сможет обработать исключение, то есть std::exception, потому что он является родителем DerivedException.
Это значит, что наиболее специализированные исключения нужно располагать первыми, то есть порядок в коде должен быть обратным.
Какой из обработчиков 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.
Это значит, что наиболее специализированные исключения нужно располагать первыми, то есть порядок в коде должен быть обратным.
👍4❤1
Нам всегда говорили, что бросать исключения из деструктора это плохая идея. Один из примеров плохого результата - это раскручивание стека при обработке исключения, когда вызываемые деструкторы приводят к повторному исключению, которое кроме как вызовом terminate() толком не обработаешь. Но что, если выбросить исключение очень хочется, ведь мы сделаем всё "аккуратно"? Давайте посмотрим на такой код:
Что тут может пойти не так, вроде же всё ОК? Одно исключение, мы его ловим. По факту, данный код приведет к вызову terminate(), так как все деструкторы по-умолчанию неявно помечены как noexcept, то есть не выбрасывающие исключений.
Если очень сильно хочется вернуться во времена до C++11, то помечаем деструктор как noexcept(false) и получаем наше исключение (не понятно только зачем):
#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-ы) позволяют хранить данные разных типов в одной области памяти. Пример:
Но можно ли наследовать union? Давайте попробуем:
Получим ошибку компиляции: enum/union cannot be used as a base class
Объединения нельзя использовать как базовый класс. Напишите в комментариях, кто знает почему)
#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
Многим известно, что std::string можно создать из указателя на char * (допустим из некой C-шной функции):
Но что будет, если нам прилетит nullptr и мы передадим его в конструктор std::string?
Можно подумать, что создастся пустая строка. В конце концов, почему бы и нет, я бы например реализовала строку именно так. На самом же деле передавать nullptr в конструктор строки нельзя и в общем случае будет UB.
На gcc у меня это приводит к std::logic_error. А вот на msvc уже будет
Ситуация немного прояснилась с выходом C++23. Создавать строку из nullptr более нельзя:
Хотя кажется, что проблему с возвратом nullptr это не решает.
#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 это не решает.
👍5❤3🔥2
Как можно оформить в C++ длинный строковый литерал и разбить его на нескольксо строк в коде?
Допустим, мы хотим разбить длинную строку в коде на несколько линий, но так чтобы переносы строк и отступы из самого кода не повлияли на результат.
Один из проcтейших способов - просто написать строки в кавычках друг за другом:
Такой вариант склеит строки без переносов
Однако, есть и другой способ: можно использовать кавычки только в начале и конце всей строки, вставив символ
как самый последний (!) на каждой строке:
При этом начинать новую строку следует с самого начала, иначе любой отступ в коде
попадет и в саму строку. Неудобно, правда?
А как же raw string literals? Те самые, что заключаются в
Да, они замечательно подходят, чтоб не экранировать разные символы, вроде кавычек и обратных слешей:
Но что будет, если взять нашу цитату, перенесенную по строкам в коде:
Оно вставит переносы строк
Мой победитель: просто строки в двойных кавычках на каждой строке. Просто, привычно, не вставляет ненужных символов и позволяет форматировать код.
Допустим, мы хотим разбить длинную строку в коде на несколько линий, но так чтобы переносы строк и отступы из самого кода не повлияли на результат.
Один из про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 и отступы из кода в результат.Мой победитель: просто строки в двойных кавычках на каждой строке. Просто, привычно, не вставляет ненужных символов и позволяет форматировать код.
👍6❤2🔥2
Многим известно как работает dynamic_cast в C++. Зная, что за указателем на базовый класс стоит объект производного, мы можем скастовать базовый до производного и вызвать его метод. Пример:
Все, что нужно для корректной работы dynamic_cast - это наличие хотя бы одного виртуального метода. И тогда мы можем в рантайме проверить результат кастования на nullptr. Если это nullptr - то мы не угадали и указатель смотрел на объект базового класса.
Обычно такой подход рассматривается как результат неудачного проектирования, но иногда все же приходится так делать. К тому же проверка указателя на nullptr довольно обычная операция.
Но что делать, если мы имеем не указатель, а ссылку на базовый класс? Оказывается, что dynamic_cast работает как ожидается и со ссылками - можно преобразовать ссылку на базовый класс в ссылку на производный. Однако, что делать, если преобразование в рантайме невозможно? Ссылка, в отличие от указателя не может указывать на nullptr. В случае ошибки преобразования dynamic_cast выбросит исключение std::bad_cast. Пример:
Однако, этот пример показывает плохой стиль программирования, потому что использует исключения для потока выполнения.
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, которая делает, то что нам нужно:
Скучно? На первый взгляд да, но в C++ всегда можно закопаться в детали =)
Во-первых, что за странное название? Почему не какой-нибудь
Оказывается, что popcount это сокращение от “population count”.
Вроде немного понятнее, но лучше не стало)
Чтож, почитаем хабр: https://habr.com/ru/articles/467083/
Кроме пространного списка задач, где для решения используется эта функция, из статьи мы узнаём
что во многих процессорах есть специальная инструкция (н-р
данное вычисление очень быстро (за такт?).
Кроме того, в GCC и CLANG есть встроенные функции, которые также вычисляют popcount очень быстро и что-то мне подсказывает, что они могут вызывать эту единственную инструкцию (а может и нет).
В GCC эта функция наз-ся
Как видим, в дебаге GCC вставит встроенную
Если же добавить -O3, то разницы не будет никакой.
Замеры делать не буду)
Давайте возьмем 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, то разницы не будет никакой.
Замеры делать не буду)
Хабр
Как используется странная инструкция popcount в современных процессорах
Это псевдорасшифровка моей презентации на !!Con 2019 . В большинстве используемых сегодня процессорных архитектур есть инструкция под названием popcount , сокращённо от 'population count'. Она делает...
👍5❤1
Давайте посмотрим на такой пример заголовочного файла (.h) c использованием указателя на incomplete тип (forward declaration):
Не будем вдаваться в корректность, посмотрим для начала на компилируемость.
Такой код, конечно же соберется, ведь компилятор знает размер указателя в байтах (размер одинаков как для определенного, так и неопределенного типа).
Если мы раскомментируем строку с shared_ptr, то код тоже скомпилируется (по крайней мере у меня собрался). Не будем говорить, про корректность его работы, но напомним, что вцелом удаление неопределенного типа - UB.
А вот если мы раскомментируем строку с unique_ptr, то код перестанет собираться.
Многие используют паттерн pimpl и потому знают, что реализацию деструктора нужно сделать в других исходниках (например .cpp), где forward declaration тип определен и проблема этим решается.
Но почему компилятор не ругается на shared_ptr?
Дело в том, что unique_ptr и shared_ptr требуют полного определения типа в разных местах. Детали сложны (и о них я не знаю), но первому нужен статический deleter, тогда как второму - динамический.
Вывод: в нашей копилке костылей еще одно неправильное решение - заменить unique_ptr на shared_ptr 😊.
#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 😊.
👍3❤1
По умолчанию, при форматировании вывода в поток булевых значений, будет использоваться
1 и 0 для true и false. Пример:
Возможно, что удобнее (особенно для отладки) выводить булевы значения строкой,
также как они задаются в коде. Для этого нужно добавить в поток манипулятор boolalpha:
Интересно то, что манипулятор действует на все последующие записи булевых значений
для этого потока. То есть он будет применяться и после ; и после std::endl.
Отменить форматирование булевых флагов можно записав в поток манипулятор
std::noboolalpha:
(Интересно, а для ввода это работает? кто проверит?)
Честно говоря, мне бы хотелось увидеть нормальные true/false по-умолчанию,
но видимо обратная совместимость.
Еще одной интересной особенностью форматирования вывода является округление
значений с плавающей точкой.
Посмотрим на вывод такого примера (на вашем окружении может быть другой результат):
Поток выбрал точность в 6 знаков после точки (мы эту точность не задавали, поэтому может быть другой),
и проведет не отсечение, а округление в последней цифре.
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