pointer to data member
#опытным
В этом посте мы рассказывали о том, что с помощью ranges и и параметра проекции можно кастомизировать алгоритмы с соответствии с определенным полем класса. Например, чтобы найти в коллекции элемент с максимальным определенным полем, то можно сделать так:
max в этом случае будет транзакцией с максимальным размером платежа.
В последней строчке используется
Если про указатели на конкретные мемберы знают не только лишь все, то это совсем дебри плюсов.
Явный тип указателя на поле класса используется так:
По сути это особый тип указателя, который хранит смещение поля относительно начала объекта в байтах. Это не специфицировано в стандарте, но примерно везде так работает.
Мы обязательно должны указать, на какой тип полей этот указатель может указывать. Таким образом указатель
Указателю на интовое поле нельзя присвоить указатель на флотовое. И наоборот, указатель
Если вы подумали, что очень узкоспециализированная вещь, то вы правы. Чуть больше универсализации здесь могут дать шаблоны:
Walk through the nooks and crannies. Stay cool.
#cppcore #memory
#опытным
В этом посте мы рассказывали о том, что с помощью ranges и и параметра проекции можно кастомизировать алгоритмы с соответствии с определенным полем класса. Например, чтобы найти в коллекции элемент с максимальным определенным полем, то можно сделать так:
struct Payment {
double amount;
std::string category;
};
auto max = *std::ranges::max_element(payments, {}, &Payment::amount);max в этом случае будет транзакцией с максимальным размером платежа.
В последней строчке используется
&Payment::amount - указатель на поле amount в классе Payment. Но если это параметр функции, то это значение какого-то типа. Но какой тип у этого указателя?Если про указатели на конкретные мемберы знают не только лишь все, то это совсем дебри плюсов.
Явный тип указателя на поле класса используется так:
struct Payment {
double amount;
std::string category;
};
double Payment::*ptr = &Payment::amount; // Here!
Payment payment{3.14, "Groceries"};
std::cout << payment.*ptr << std::endl;
// OUTPUT:
// 3.14double Payment::* ptr = &Payment::amount;
// тип указателя имя указателя инициализатор
По сути это особый тип указателя, который хранит смещение поля относительно начала объекта в байтах. Это не специфицировано в стандарте, но примерно везде так работает.
Мы обязательно должны указать, на какой тип полей этот указатель может указывать. Таким образом указатель
ptr может указывать на любое поле класса Payment, имеющее тип double. То есть:struct Type {
int a;
int b;
float c;
};
int Type::*p = nullptr;
p = &Type::a; // OK, a is int
p = &Type::b; // OK, b is int
p = &Type::c; // ERROR! c is floatУказателю на интовое поле нельзя присвоить указатель на флотовое. И наоборот, указатель
p работает с любыми полями типа int.Если вы подумали, что очень узкоспециализированная вещь, то вы правы. Чуть больше универсализации здесь могут дать шаблоны:
// Takes pointer to any data member for any type
template<typename T, typename FieldType>
void print_field(const T& obj, FieldType T::*field) {
std::cout << obj.*field << std::endl;
}
Payment payment{3.14, "Groceries"};
Type t(42, 69, 3.14);
print_field(payment, &Payment::amount);
print_field(payment, &Payment::category);
print_field(t, &Type::a);
print_field(t, &Type::b);
print_field(t, &Type::c);
// OUTPUT
// 3.14
// Groceries
// 42
// 69
// 3.14
print_field может печатать значение любого поля любого класса по его указателю. Обратите внимание на шаблонную сигнатуру.Walk through the nooks and crannies. Stay cool.
#cppcore #memory
3❤23🔥14👍11
WAT
#опытным
Спасибо, @Ivaneo, за любезно предоставленный примерчик в рамках рубрики #ЧЗХ.
Всегда ли nullptr указатель равен нулю?
Казалось бы в названии дан ответ:
Но в общем случае это неправда! Смотрим на пример:
nullptr указатель равен совсем не нулю, как декларировалось в начале main.
WAT? Что за фокусы с пропажей нуля?
Во вчерашнем посте мы рассказывали об особом типе указателя - pointer to data member. Этот указатель, которым и является
И в большинстве случаев эта информация представляет собой просто смещение поля относительно начала объекта в байтах.
Однако нулевое смещение используется для локации самого первого поля класса. Поэтому в байтовом представлении неинициализированный указатель не может быть нулем.
Вместо этого обычно используется число -1, которое в байтовом представлении как раз выглядит как все единички:
С помощью указателей на поля класса можно кстати наглядно изучать выравнивание и упаковку полей с объект:
Опять же, интересный уголок плюсов.
Walk through the nooks and crannies. Stay cool.
#cppcore #memory
#опытным
Спасибо, @Ivaneo, за любезно предоставленный примерчик в рамках рубрики #ЧЗХ.
Всегда ли nullptr указатель равен нулю?
Казалось бы в названии дан ответ:
int * p = nullptr;
std::cout << std::boolalpha << (p == nullptr) << "\n";
std::cout << std::hex << std::bit_cast<std::uintptr_t>(p) << "\n";
// OUTPUT
// true
// 0
Но в общем случае это неправда! Смотрим на пример:
struct A {
int i;
};
int main() {
int A::* p = 0;
std::cout << std::boolalpha << (p == nullptr) << "\n";
std::cout << std::hex << std::bit_cast<std::uintptr_t>(p) << "\n";
std::cout << std::boolalpha << (std::bit_cast<std::uintptr_t>(p) == 0xffffffffffffffff) << "\n";
}
// OUTPUT:
// true
// ffffffffffffffff
// truenullptr указатель равен совсем не нулю, как декларировалось в начале main.
WAT? Что за фокусы с пропажей нуля?
Во вчерашнем посте мы рассказывали об особом типе указателя - pointer to data member. Этот указатель, которым и является
p из примера, по сути хранит информацию о том, как в объекте найти нужное поле класса.И в большинстве случаев эта информация представляет собой просто смещение поля относительно начала объекта в байтах.
Однако нулевое смещение используется для локации самого первого поля класса. Поэтому в байтовом представлении неинициализированный указатель не может быть нулем.
Вместо этого обычно используется число -1, которое в байтовом представлении как раз выглядит как все единички:
std::cout << std::hex << static_cast<long long int>(-1) << "\n";
// OUTPUT:
// ffffffffffffffff
С помощью указателей на поля класса можно кстати наглядно изучать выравнивание и упаковку полей с объект:
struct Type {
double a;
char b;
float c;
long long d;
short e;
unsigned f;
};
std::cout << std::dec << std::bit_cast<std::uintptr_t>(&Type::a) << "\n";
std::cout << std::dec << std::bit_cast<std::uintptr_t>(&Type::b) << "\n";
std::cout << std::dec << std::bit_cast<std::uintptr_t>(&Type::c) << "\n";
std::cout << std::dec << std::bit_cast<std::uintptr_t>(&Type::d) << "\n";
std::cout << std::dec << std::bit_cast<std::uintptr_t>(&Type::e) << "\n";
std::cout << std::dec << std::bit_cast<std::uintptr_t>(&Type::f) << "\n";
// OUTPUT:
// 0
// 8
// 12
// 16
// 24
// 28Опять же, интересный уголок плюсов.
Walk through the nooks and crannies. Stay cool.
#cppcore #memory
❤21👍12🔥8🤯4❤🔥2
Сколько весит объект полиморфного класса?
#новичкам
Частый вопрос с собеседований про размеры объектов различных классов. Даже в бэкэндерских конторах, в которых никогда в жизни не учитывали эти размеры. Но такие вопросы раскрывают знание базы, а считается, что без ее знания вы не можете писать нормальный код.
У нас уже был пост про размер объекта пустого класса.
А что если это будет класс с виртуальными методами? Сколько тогда будет весить этот класс?
Мы знаем, что для каждого класса, имеющего виртуальные методы, создается глобальная таблица виртуальных функций. В ней находятся конкретные адреса виртуальных методов конкретно этого класса. И именно к ней обращаются, когда хотят порешать вопросики, какой метод вызвать.
Таблица одна на все объекты заданного класса. И им каким-то образом нужно получить доступ к этой таблице.
Учитывайте, что нельзя захардкодить эту информацию по статическому типу объекта(например SomeClass& или SomeClass*), потому что под его личиной может скрываться наследник.
Значит надо ее класть в каждый объект. И самое простое - положить в них указатель на свою таблицу виртуальных функций. Так и делают на самом деле. Этот указатель называют vptr.
Соответственно размер класса зависит от битности системы. Для 64-бит указатель имеет размер 8 байт(64 бита) поэтому и размер класса SomeClass будет 8 байт.
Пустые наследники SomeClass кстати тоже будут иметь размер 8 из-за того, что им нужно лишь другое значения указателя.
Если вы добавите еще полей, то размер увеличится в соответствии с размером дополнительных полей и выравниванием. Если вы используете множественное наследование, то тоже увеличится, но об этом как-нибудь потом поговорим.
Be lightweight. Stay cool.
#cppcore #interview
#новичкам
Частый вопрос с собеседований про размеры объектов различных классов. Даже в бэкэндерских конторах, в которых никогда в жизни не учитывали эти размеры. Но такие вопросы раскрывают знание базы, а считается, что без ее знания вы не можете писать нормальный код.
У нас уже был пост про размер объекта пустого класса.
А что если это будет класс с виртуальными методами? Сколько тогда будет весить этот класс?
struct SomeClass {
virtual ~SomeClass() = default;
virtual void Process() {
std::cout << "Process" << std::endl;
}
};Мы знаем, что для каждого класса, имеющего виртуальные методы, создается глобальная таблица виртуальных функций. В ней находятся конкретные адреса виртуальных методов конкретно этого класса. И именно к ней обращаются, когда хотят порешать вопросики, какой метод вызвать.
Таблица одна на все объекты заданного класса. И им каким-то образом нужно получить доступ к этой таблице.
Учитывайте, что нельзя захардкодить эту информацию по статическому типу объекта(например SomeClass& или SomeClass*), потому что под его личиной может скрываться наследник.
Значит надо ее класть в каждый объект. И самое простое - положить в них указатель на свою таблицу виртуальных функций. Так и делают на самом деле. Этот указатель называют vptr.
Соответственно размер класса зависит от битности системы. Для 64-бит указатель имеет размер 8 байт(64 бита) поэтому и размер класса SomeClass будет 8 байт.
std::cout << sizeof(SomeClass) << std::endl;
// OUTPUT
// 8
Пустые наследники SomeClass кстати тоже будут иметь размер 8 из-за того, что им нужно лишь другое значения указателя.
Если вы добавите еще полей, то размер увеличится в соответствии с размером дополнительных полей и выравниванием. Если вы используете множественное наследование, то тоже увеличится, но об этом как-нибудь потом поговорим.
Be lightweight. Stay cool.
#cppcore #interview
1❤21👍15🔥7😁7🆒1
enum class
#новичкам
Перечисления пришли в С++ еще из С и отлично живут. Однако плюсовикам не очень с ними комфортно работать с силу наследования слабой типизации и неявных преобразований enum'ов в числовые типы и в другие enum'ы
В С++11 появился новый тип перечислений - scoped enumerations. Или ограниченные областью видимости перечисления. Определяются они так:
Он решает две большие проблемы обычных перечислений:
👉🏿 Обычные перечисления неявно преобразуются в int и обратно, что вызывает ошибки, когда не предполагается использование перечисления в качестве целого числа.
Можно например попробовать получить следующее значение перечисления, просто прибавив единицу:
Что значит прибавить красному цвету единицу - решительно непонятно.
Неявные преобразования enum class'ов же запрещено:
Если вам сильно нужно преобразовать перечислитель к числу, то вы это должны сделать явно:
👉🏿 Обычные перечисления экспортируют свои перечислители в окружающую область видимости, вызывая конфликты имён с другими сущностями в этой окружающей области:
У scoped enum'ов такой проблемы нет. Имена перечислителей находятся в скоупе своего перечисления:
И все прекрасно компилируется.
С учетом неймспейсов и любви к явным кастам в коммьюнити, в С++ лучше использовать enum class'ы вместо обычных перечислений.
Protect your scope. Stay cool.
#cppcore #cpp11
#новичкам
Перечисления пришли в С++ еще из С и отлично живут. Однако плюсовикам не очень с ними комфортно работать с силу наследования слабой типизации и неявных преобразований enum'ов в числовые типы и в другие enum'ы
В С++11 появился новый тип перечислений - scoped enumerations. Или ограниченные областью видимости перечисления. Определяются они так:
enum class Enumeration {CATEGORY1, CATEGORY2, CATEGORY3};Он решает две большие проблемы обычных перечислений:
👉🏿 Обычные перечисления неявно преобразуются в int и обратно, что вызывает ошибки, когда не предполагается использование перечисления в качестве целого числа.
Можно например попробовать получить следующее значение перечисления, просто прибавив единицу:
cpp
enum Color { RED, GREEN, BLUE };
Color c = RED;
Color next = c + 1; // Implicit conversion to int and visa versa!
Что значит прибавить красному цвету единицу - решительно непонятно.
Неявные преобразования enum class'ов же запрещено:
enum class Color { RED, GREEN, BLUE };
Color c = Color::RED;
Color next = c + 1; // ERROR!Если вам сильно нужно преобразовать перечислитель к числу, то вы это должны сделать явно:
Color c = Color::RED;
Color next = static_cast<Color>(static_cast<int>(c) + 1);
👉🏿 Обычные перечисления экспортируют свои перечислители в окружающую область видимости, вызывая конфликты имён с другими сущностями в этой окружающей области:
enum Color { RED, GREEN, BLUE };
enum TrafficLight { RED, YELLOW, GREEN }; // ERROR!
void graphics_library() {
Color c = RED;
}У scoped enum'ов такой проблемы нет. Имена перечислителей находятся в скоупе своего перечисления:
enum class Color1 { RED, GREEN, BLUE };
enum class Color2 { RED, GREEN, BLUE };
void graphics_library() {
Color1 c1 = Color1::RED;
Color2 c2 = Color2::RED;
}И все прекрасно компилируется.
С учетом неймспейсов и любви к явным кастам в коммьюнити, в С++ лучше использовать enum class'ы вместо обычных перечислений.
Protect your scope. Stay cool.
#cppcore #cpp11
👍40❤16🔥15