Если не указать атрибут наследования, то для производного класса объявленного как class будет приватное наследование, а для struct - публичное:
#include <iostream>
#include <map>
class Base
{
public:
void f() {}
};
class Derived1 : Base // не указан атрибут наследования
{
};
struct Derived2 : Base // не указан атрибут наследования
{
};
int main()
{
Derived1 d1;
Derived2 d2;
d1.f(); // Ошибка, private inheritance
d2.f(); // OK, public inheritance
return 0;
}
🫡6
Начиная с С++11 параметр типа шаблона можно сделать дружественным, пример:
Класс Bob стал другом класса Alice<Bob> и получил доступ до его приватных членов.
#include <iostream>
template <typename T>
class Alice
{
friend T; // не friend class T
int priv_int = 4;
};
class Bob
{
public:
void print(const Alice<Bob>& alice) {
std::cout << alice.priv_int // Bob is friend of Alice
<< std::endl;
}
};
int main()
{
Alice<Bob> a;
Bob b;
b.print(a); // 4
return 0;
}
Класс Bob стал другом класса Alice<Bob> и получил доступ до его приватных членов.
🔥8
Можно ли вызывать виртуальные функции из конструкторов и деструкторов? Можно, но поведение может быть неожиданным (и такого лучше избегать). Пример:
Как видим при вызове конструтора производного класса Derived сперва вызовется конструктор базового. При этом пока выполняется конструктор базового класса, производная часть объекта остается неинициализированной. Поэтому конструктору базового класса при вызове виртуального метода было бы опасно вызывать переопределенную версию производного, так как такой метод мог бы обратиться к объектам производного класса. Поэтому C++ при создании (и удалении) объекта рассматривает его тип как изменяющийся. При вызове вртуального метода из базового конструктора вызовется метод базового класса, а при вызове из производного - этого производного. После окончания вызова конструктора базового класса вызовется конструктор производного.
При вызове деструктора будет похожая ситуация, но в обратном порядке. При вызове деструктора базового класса производная часть уже будет удалена то есть уже будет вызыван деструктор производного класса. При этом виртуальный метод в базовом деструкторе будет вызван для базового типа.
#include <iostream>
class Base
{
public:
Base() {
std::cout << "Base::Base" << std::endl;
f1(); // всегда вызов Base::f1, даже если он переопределен в наследнике
}
virtual ~Base() {
std::cout << "Base::~Base" << std::endl;
f2(); // всегда вызов Base::f2, даже если он переопределен в наследнике
};
virtual void f1() { std::cout << "Base::f1" << std::endl; }
virtual void f2() { std::cout << "Base::f2" << std::endl; }
};
class Derived : public Base
{
public:
Derived() {
std::cout << "Derived::Derived" << std::endl;
f1();
}
~Derived() override {
std::cout << "Derived::~Derived" << std::endl;
f2();
};
void f1() override { std::cout << "Derived::f1" << std::endl; }
void f2() override { std::cout << "Derived::f2" << std::endl; }
};
int main() {
Derived d;
std::cout << "============" << std::endl;
d.f2();
std::cout << "************" << std::endl;
}
// Output:
// Base::Base
// Base::f1
// Derived::Derived
// Derived::f1
// ============
// Derived::f2
// ************
// Derived::~Derived
// Derived::f2
// Base::~Base
// Base::f2
Как видим при вызове конструтора производного класса Derived сперва вызовется конструктор базового. При этом пока выполняется конструктор базового класса, производная часть объекта остается неинициализированной. Поэтому конструктору базового класса при вызове виртуального метода было бы опасно вызывать переопределенную версию производного, так как такой метод мог бы обратиться к объектам производного класса. Поэтому C++ при создании (и удалении) объекта рассматривает его тип как изменяющийся. При вызове вртуального метода из базового конструктора вызовется метод базового класса, а при вызове из производного - этого производного. После окончания вызова конструктора базового класса вызовется конструктор производного.
При вызове деструктора будет похожая ситуация, но в обратном порядке. При вызове деструктора базового класса производная часть уже будет удалена то есть уже будет вызыван деструктор производного класса. При этом виртуальный метод в базовом деструкторе будет вызван для базового типа.
👍4
У шаблона класса могут быть статические члены, при этом у каждого экземпляра для любого конкретного типа будет свой экземпляр:
Как видим у экземляра Class<float> свои экземпляры для статических членов size() и sz, а у Class<int> свои, отличные от них.
#include <iostream>
template <typename T>
class Class
{
public:
static int size() {
return sz;
}
static int sz;
T value;
};
template <typename T>
int Class<T>::sz = 0; // дефолтное значение для всех экземпляров шаблонов
int main() {
Class<float> fc1, fc2, fc3;
Class<int> ic;
fc1.sz = 3;
ic.sz = 4;
std::cout << fc1.size() << " " << fc2.size()
<< " " << Class<float>::size(); // 3 3 3
std::cout << std::endl;
std::cout << ic.size(); // 4
}
Как видим у экземляра Class<float> свои экземпляры для статических членов size() и sz, а у Class<int> свои, отличные от них.
❤🔥6
Давайте набросаем простой класс, который поможет отследить вызов конструктора и деструктора:
Далее мы просто создадим временное значение Xray:
Как видим, деструктор вызовется в той же строке кода, что и конструктор, то есть до вывода "Wait a sec", то есть до закрываюшей скобки.
Теперь же создадим ссылку (констанную lvalue) на временный объект:
Получили другой и возможно ожидаемый для первого случая вывод.
В чем же дело? Виноват механизм продления времени жизни временных объектов, который звучит так:
Прочитать подробнее можно в источнике.
struct Xray
{
Xray(std::string value)
: mValue(std::move(value))
{
std::cout << "Xray ctor, value is " << mValue << std::endl;
}
Xray(const Xray& other)
: mValue(other.mValue)
{
std::cout << "Xray const& ctor, value is " << mValue << std::endl;
}
~Xray()
{
std::cout << "~Xray dtor, value is " << mValue << std::endl;
}
std::string mValue;
};
Далее мы просто создадим временное значение Xray:
void main()
{
// Вывод: Xray ctor, value is 1
// Вывод: Xray dtor, value is 1
Xray{"1"};
std::cout << "Wait a sec" << std::endl;
}
Как видим, деструктор вызовется в той же строке кода, что и конструктор, то есть до вывода "Wait a sec", то есть до закрываюшей скобки.
Теперь же создадим ссылку (констанную lvalue) на временный объект:
void main()
{
// Вывод: Xray ctor, value is 1
const Xray& xrayRef = Xray{"1"};
// Вывод: xrayRef value is 1
std::cout << "xrayRef value is " << xrayRef.mValue << std::endl;
} // Вывод: Xray dtor, value is 1
Получили другой и возможно ожидаемый для первого случая вывод.
В чем же дело? Виноват механизм продления времени жизни временных объектов, который звучит так:
Если вы приняли временный объект по константной lvalue ссылке или rvalue ссылкe, то его время жизни будет продлено до времени жизни ссылки.
Прочитать подробнее можно в источнике.
PVS-Studio
Продление жизни временных значений в С++: рецепты и подводные камни
Прочитав эту статью, вы узнаете следующее: способы, которыми можно продлить время жизни временного объекта в С++; рекомендации и подводные камни этого механизма, с которыми может столкнуться С...
👍3
Прохожу курс, там сказано сравните время вставки в конец вектора и дека миллиона и 5 млн случайных чисел. Напишем код:
После запуска на 5 разных платформах получилось, что дек быстрее на 3, на одной примерно также, а на последней (MSVC) медленнее. По мнению авторов курса, дек быстрее, тк там нет копирования при ресайзе (reserve для вектора сказано не использовать).
Лично у меня два вывода: 1) берем вектор и не паримся, он все равно очень быстрый 2) такие вопросы без окружения в тестирование выносить - дурной тон.
#include <vector>
#include <deque>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <chrono>
#include <string>
void fill_random_vector(size_t size)
{
std::vector<int> v;
for (size_t i = 0; i < size; ++i) {
v.push_back(rand());
}
std::cout << "v[" << size - 1 << "] = " << v[size - 1] << std::endl;
}
void fill_random_deque(size_t size)
{
std::deque<int> d;
for (size_t i = 0; i < size; ++i) {
d.push_back(rand());
}
std::cout << "d[" << size - 1 << "] = " << d[size - 1] << std::endl;
}
template<typename F>
void measure_time(F func, const std::string& text)
{
auto begin = std::chrono::steady_clock::now();
func();
auto end = std::chrono::steady_clock::now();
std::cout << text << " "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count()
<< " ms" << std::endl;
}
int main()
{
srand((unsigned)time(0));
measure_time([](){ fill_random_vector(1'000'000); }, "fill_random_vector 1'000'000");
measure_time([](){ fill_random_deque(1'000'000); }, "fill_random_deque 1'000'000");
measure_time([](){ fill_random_vector(5'000'000); }, "fill_random_vector 5'000'000");
measure_time([](){ fill_random_deque(5'000'000); }, "fill_random_deque 5'000'000");
return 0;
}
После запуска на 5 разных платформах получилось, что дек быстрее на 3, на одной примерно также, а на последней (MSVC) медленнее. По мнению авторов курса, дек быстрее, тк там нет копирования при ресайзе (reserve для вектора сказано не использовать).
Лично у меня два вывода: 1) берем вектор и не паримся, он все равно очень быстрый 2) такие вопросы без окружения в тестирование выносить - дурной тон.
👍4
Удаляет ли std::vector элементы (иначе, вызывает ли деструктор) при resize() и clear()? И если да, то в каком порядке?
Напишем код:
Проанализируем вывод:
Несмотря на то, что capacity не меняется, для элементов вызывается деструктор. При этом деструктор неожиданно вызывается в порядке от первого элемента к последнему, что противоречит обычному правилу: деструктор вызывается в порядке обратном конструктору. Хотя следует упомянуть, что порядок удаления для вектора не определен (FIXME) и может быть другим/любым. Однако же для обычного массива он будет ожидаемым - сперва удалится последний элемент.
Напишем код:
#include <iostream>
#include <vector>
struct DtorLog
{
DtorLog(int v = 0) : value(v) {}
~DtorLog() { std::cout << "~" << value << std::endl; }
int value;
};
int main()
{
{
std::vector<DtorLog> v;
v.reserve(10);
for (int i = 1; i <= 5; ++i) {
v.emplace_back(i);
}
std::cout << "resize " << v.capacity() << std::endl;
v.resize(3);
std::cout << "clear " << v.capacity() << std::endl;
v.clear();
std::cout << "shrink_to_fit " << v.capacity() << std::endl;
v.emplace_back(6);
v.emplace_back(7);
v.shrink_to_fit();
std::cout << "end " << v.capacity() << std::endl;
}
std::cout << "array" << std::endl;
DtorLog a[3];
for (int i = 0; i < 3; ++i) {
a[i].value = i;
}
return 0;
}
Проанализируем вывод:
resize 10
~4
~5
clear 10
~1
~2
~3
shrink_to_fit 10
~6
~7
end 2
~6
~7
array
~2
~1
~0
Несмотря на то, что capacity не меняется, для элементов вызывается деструктор. При этом деструктор неожиданно вызывается в порядке от первого элемента к последнему, что противоречит обычному правилу: деструктор вызывается в порядке обратном конструктору. Хотя следует упомянуть, что порядок удаления для вектора не определен (FIXME) и может быть другим/любым. Однако же для обычного массива он будет ожидаемым - сперва удалится последний элемент.
👍6
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