Aether
|
|
« : 04-11-2017 15:22 » |
|
Доброго вечера. Вроде созрел для более основательного подхода к "С++", но упёрся в одну трудность, которую я решаю, но явно коряво. Суть дела: есть некие структуры (классы) A, B, C. Их экземпляры создаются в основном коде программы отдельно в разных местах, но мне их надо свести в единый список, который можно обрабатывать перебором объектов. Как я поступал в "С": создавал массив в котором хранил указатели на структуры в виде void* и указатели на функции обработчики этих структур вида void(*)(void*). Плюс к этому функцию регистрации объектов в этом массиве. Как мне это видится в "С++": создать некий класс предок F, в котором функцию обработчик сделать виртуальной, чтобы все классы были унифицированы и имели обработчики по умолчанию. А вот дальше проблема: массив чего создавать? Все классы имеют разный размер, значит опять массив указателей типа void*? Или же массив указателей на класс F? Но тогда компилятор не поймёт какой обработчик где вызывать. В общем запутался что-то, а может к выходным перегрелся.
|
|
|
Записан
|
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #1 : 04-11-2017 15:44 » |
|
Массив базового класса. Методы виртуальные. Это значит, что с каждым классом идет таблица виртуальных функций. И Компилятор сам тянет нужную функцию.
|
|
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
oktonion
Постоялец
Offline
|
|
« Ответ #2 : 04-11-2017 15:49 » |
|
При реализации виртуальной функции в каждом классе и вызове ее через указатель на F будет вызываться соответствующая реализация из наследника. В этом и смысл "виртуальности" функций. Обычно базовый класс с виртуальными функциями представляется как "интерфейс" ("контракт") который реализуется в классах наследниках. Затем через указатель на этот "интерфейс" ты можешь дергать функции наследников, ничего не зная об их типах и индивидуальных реализациях, но получая при этом результаты из их собственных реализаций "интерфейсных" функций единообразно в соответствии с "контрактом", прописанном в интерфейсе. struct ISpeech { virtual void talk() = 0; };
struct Dog: ISpeech { void talk() {std::cout << "wow";} };
struct Cat: ISpeech { void talk() {std::cout << "meow";} };
struct Human: ISpeech { void talk() {std::cout << "to be or not to be...";} }; Все классы из примера реализуют интерфейс "Говорить", но говорят по своему каждый. Имея указатели на базовый интерфейс можно вызывать реализации из каждого класса наследника, не имея при этом данных об их типах. Хоть массив ISpeech* заводи и дергай talk().
|
|
« Последнее редактирование: 04-11-2017 15:56 от oktonion »
|
Записан
|
|
|
|
Aether
|
|
« Ответ #3 : 04-11-2017 16:03 » |
|
Спасибо. Кое-что проясняется.
|
|
|
Записан
|
|
|
|
Михалыч
|
|
« Ответ #4 : 05-11-2017 07:34 » |
|
Много лет назад, когда я тоже только-только начинал разбираться с С++, я, пока в голове все было свежО, свел все для себя важное в несколько коротких статей, чтобы потом, при случае (ну и если кому пригодится) не копаться по куче разных книжек... Они есть в разделе "Статьи". Начало - тут, а дальше сам смотри. https://club.shelek.ru/viewart.php?id=239Буду рад, если поможет... Помнится, старался "разжевывать" максимально понятно.
|
|
|
Записан
|
Поживем - увидим... Доживем - узнаем... Выживу - учту
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #5 : 05-11-2017 08:26 » |
|
Aether, только учитывай, что "виртуальность" хранится в самом объекте (таблица адресов в начале объекта), то есть уже нельзя использовать нечто вроде memset(this,0,sizeof(*this));
также как и нельзя в бинарном виде сохранять тело объекта в файл (в поток) - после восстановления будут сюрпризы
|
|
|
Записан
|
|
|
|
Aether
|
|
« Ответ #6 : 05-11-2017 13:32 » |
|
Буду рад, если поможет...
Я думаю поможет. Тут такая штука, что со временем я что-то забываю, тоже пытаюсь систематизироваться, но выходит не всегда. Aether, только учитывай, что "виртуальность" хранится в самом объекте (таблица адресов в начале объекта), то есть уже нельзя использовать нечто вроде memset(this,0,sizeof(*this));
Я уже понял, что в "С" примерно это же и делал, только явно. Сейчас проще, так как я сопоставляю аналогии. Есть ещё такой нюанс, который не понятен по внутренней структуре: например, конструктор копирования нужно писать: object(const object& x)... а если просто в функцию необходимо передать ссылку на объект, то func(object& x)... как const работает изнутри в данном случае? также как и нельзя в бинарном виде сохранять тело объекта в файл (в поток) - после восстановления будут сюрпризы
Это зависит от линковки?
|
|
|
Записан
|
|
|
|
oktonion
Постоялец
Offline
|
|
« Ответ #7 : 05-11-2017 14:52 » |
|
Есть ещё такой нюанс, который не понятен по внутренней структуре: например, конструктор копирования нужно писать: object(const object& x)... а если просто в функцию необходимо передать ссылку на объект, то func(object& x)... как const работает изнутри в данном случае? Конструктор копирования пишется через константную ссылку потому, что объект из которого мы копируем данные себе он не изменяет. Вот и все. Это хорошая практика и ее нужно вводить везде, где это по логике программы так. Если в функции func объект x будет изменен, то тогда его стоит передавать через неконстантную ссылку, если не будет - нужно точно так же писать func(const object&x). Смысл модификатора const здесь в том, что программист, написавший функцию, как бы говорит этим: я гарантирую что не буду никаким образом менять переданный мне по ссылке объект внутри моей функции. Сам по себе модификатор const никаким образом объект не меняет, но позволяет компилятору проводить некоторые оптимизации исходя из того что объект меняться не будет, а так же показывает намерения данной функции программисту, который будет функцию эту использовать. также как и нельзя в бинарном виде сохранять тело объекта в файл (в поток) - после восстановления будут сюрпризы Это зависит от линковки? Речь идет о том что как только у тебя появляются виртуальные функции в классе - тут же появляется неявно прилепленная к нему таблица виртуальных адресов. И адреса эти меняются от компиляции к компиляции. Потому вытащив бинарные данные (к примеру из файла) с другими виртуальными адресами и скопировав все это в объект класса можно получить обращение совсем не к тем виртуальным реализациям функций, а скорее всего вообще мимо всего.
|
|
|
Записан
|
|
|
|
Aether
|
|
« Ответ #8 : 05-11-2017 15:08 » |
|
Если в функции func объект x будет изменен, то тогда его стоит передавать через неконстантную ссылку, если не будет - нужно точно так же писать func(const object&x).
Если я объявляю функцию через const& x, но потом вызываю метод, например, x.get(), который не изменяет содержимое экземпляра, то будет ли с точки зрения компилятора это попыткой изменения? Или в этом случае get() нужно тоже оформить определённым образом? И адреса эти меняются от компиляции к компиляции.
Это понятно. А можно ли сохранять бинарные данные, используя за базу адрес первого аргумента класса? Или же это вообще плохая практика и стоит явно выводить данные в поток поимённо?
|
|
|
Записан
|
|
|
|
oktonion
Постоялец
Offline
|
|
« Ответ #9 : 05-11-2017 15:45 » |
|
Если я объявляю функцию через const& x, но потом вызываю метод, например, x.get(), который не изменяет содержимое экземпляра, то будет ли с точки зрения компилятора это попыткой изменения? Или в этом случае get() нужно тоже оформить определённым образом? У константного объекта можно вызывать только константные функции. Тоесть, да, нужно. Выглядеть это будет вот так к примеру: struct object { int get() const; }; Это понятно. А можно ли сохранять бинарные данные, используя за базу адрес первого аргумента класса? Или же это вообще плохая практика и стоит явно выводить данные в поток поимённо? Вообще по стандарту такие "бинарные" copy-paste можно делать только с POD-типами. Все остальное это нарушение стандарта и ведет к неопределенному поведению. Применительно к классу это означает что так его копировать можно только если он не содержит никаких функций (хоть виртуальных, хоть нет), а так же все его члены являются тоже POD-типами. Сохранение/загрузка данных довольно больная тема в языках со строгой типизацией и в C++ особенно. Обычно для сложных типов используют какой то сериализатор, который умеет корректно сохранять данные класса и загружать их.
|
|
« Последнее редактирование: 05-11-2017 15:48 от oktonion »
|
Записан
|
|
|
|
Aether
|
|
« Ответ #10 : 05-11-2017 15:57 » |
|
Спасибо, степень понимания увеличилась.
|
|
|
Записан
|
|
|
|
RXL
Технический
Администратор
Offline
Пол:
|
|
« Ответ #11 : 05-11-2017 16:18 » |
|
Тип с модификатором const следует рассматривать как другой тип. При этом есть правила приведения: неконстантный тип можно привести к константному, но не наоборот.
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
oktonion
Постоялец
Offline
|
|
« Ответ #12 : 05-11-2017 16:35 » |
|
Тип с модификатором const следует рассматривать как другой тип. При этом есть правила приведения: неконстантный тип можно привести к константному, но не наоборот.
Не совсем так. "Константность" объекта можно снять, если это например указатель или ссылка, через const_cast. Если же объект на самом деле "константный" то попытка его изменения при этом приведет к неопределенному поведению. Однако в большинстве случаев такого не требуется и следует придерживаться "неизменяемости" объекта если ты его получил по const ссылке или указателю.
|
|
« Последнее редактирование: 05-11-2017 16:38 от oktonion »
|
Записан
|
|
|
|
RXL
Технический
Администратор
Offline
Пол:
|
|
« Ответ #13 : 05-11-2017 17:13 » |
|
Это явное приведение. Неявно оно не произойдет. Тоже самое с методами: для const объекта можно вызвать только const методы; для не const - не const методы, плюс через приведение к const доступны const методы.
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
oktonion
Постоялец
Offline
|
|
« Ответ #14 : 05-11-2017 18:07 » |
|
Это явное приведение. Неявно оно не произойдет. Тоже самое с методами: для const объекта можно вызвать только const методы; для не const - не const методы, плюс через приведение к const доступны const методы.
Все так. Ну и так как неконстантный объект может быть неявно приведен к константному, то все const функции доступны из неконстантного объекта без явных приведений (хотя при наличии двух версий одной функции - const и non-const вызываться будет та, которая соответствует "константности" самого объекта).
|
|
|
Записан
|
|
|
|
RXL
Технический
Администратор
Offline
Пол:
|
|
« Ответ #15 : 05-11-2017 18:38 » |
|
И иллюстрация к тексту: #include <stdio.h>
class C { public: int get() const { return 1; } int get() { return 2; } };
int main() { C o1; const C o2;
printf("o1: %d\n", o1.get()); printf("o2: %d\n", o2.get()); return 1; } $ gcc -Wextra -o const const.cpp $ ./const o1: 2 o2: 1
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
Aether
|
|
« Ответ #16 : 05-11-2017 19:16 » |
|
Как-то смысл теряется. Во-первых, зачем нужен константный класс? Похоже, это затычка "на всякий случай". И непонятно тогда зачем нужен константный метод? Я изначально понял, что модификатор const к методу позволяет заблокировать изменение указанного класса в теле этого метода. Короче, нужно избегать пользоваться сомнительными решениями.
|
|
|
Записан
|
|
|
|
oktonion
Постоялец
Offline
|
|
« Ответ #17 : 05-11-2017 19:50 » |
|
модификатор const к методу позволяет заблокировать изменение указанного класса в теле этого метода. Верно понял. А еще как следствие можно сказать что только константные методы можно вызывать у константного объекта. зачем нужен константный класс? Не класс, а объект такого класса. Зачем нужен объект класса с модификатором const? - Затем чтобы контролировать операции с ним к примеру. Модификатор const "запрещает" на уровне ошибки компиляции модифицировать объект. "Запрещает" к кавычках потому что есть всякие хаки с memcpy, void*, union и т.п. нарушающие стандарт обычно, но в терминах программирования на языке C++ - нельзя изменять константные объекты. И тут вступают в дело функции с модификатором const: зачем нужен константный метод? Затем что это единственные функции что можно вызывать у константного объекта. RXL привел класс с двумя геттерами лишь для примера, естественно достаточно одного лишь int get() const - не изменяет объект -> можно вызывать как у константного так и у неконстантного объекта. Ну и немного кода для понимания: class MySuperInt { public: MySuperInt(int val): _val(val) {}
int get() const {return _val;}
private: int _val; };
const MySuperInt mysuperint(10); Если бы функция get() не была бы помечена как const, у объекта mysuperint не было бы возможности получить значение.
|
|
|
Записан
|
|
|
|
RXL
Технический
Администратор
Offline
Пол:
|
|
« Ответ #18 : 05-11-2017 21:05 » |
|
Использование const позволяет снизить вероятность ошибки. Если метод не меняет состояние объекта, стоит объявить его const. Если параметр функции не меняется, его следует объявить const. В итоге, когда все логично и согласованно, ошибки в новом коде находятся быстро. А мудрить своей головой не стоит, это только мешает компилятору.
Добавлено через 5 минут и 25 секунд: Aether, для примера посмотри документацию libc (модификатор const родом из Си), на объявления функций, обрати внимание на частоту использования const в параметрах.
|
|
« Последнее редактирование: 05-11-2017 21:11 от RXL »
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
Aether
|
|
« Ответ #19 : 06-11-2017 20:23 » |
|
Непонимание в примере RXL вот в чём: 1) Если будут описаны два get() метода, то для константного экземпляра будет вызван константный метод, для не константного - не константный. Понятно. 2) Если будет описан только не константный метод, то константный экземпляр в случае использования метода get() должен выдать ошибку? 3) Если будет описан только константный метод, то в не константном экземпляре будет выполнено предварительное приведение к константному экземпляру? И ошибки не будет?
|
|
|
Записан
|
|
|
|
oktonion
Постоялец
Offline
|
|
« Ответ #20 : 06-11-2017 20:58 » |
|
2) Да, конечно. У const объекта нельзя вызывать неконстантные функции. 3) Да, все так. Будет неявное приведение к const объекта и вызовется константная функция.
Запрет один - нельзя вызвать неконстантную функцию у константного объекта, так как объект должен быть неизменяемым, а неконстантные функции объект меняют (могут менять).
|
|
« Последнее редактирование: 06-11-2017 21:02 от oktonion »
|
Записан
|
|
|
|
RXL
Технический
Администратор
Offline
Пол:
|
|
« Ответ #21 : 06-11-2017 22:07 » |
|
oktonion, поправлю: не функции, а методы. Методы изменяют состояние объекта, функции объекта не имеют и не могут иметь модификатор const.
|
|
« Последнее редактирование: 06-11-2017 22:09 от RXL »
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
oktonion
Постоялец
Offline
|
|
« Ответ #22 : 06-11-2017 22:18 » |
|
RXL, хм, просто есть функции - члены (member functions) и есть обычные свободные функции (free functions). И то что первые называют еще и методами не отменяет что они функции. Не очень понятно как они не могут иметь модификатор const.
|
|
|
Записан
|
|
|
|
RXL
Технический
Администратор
Offline
Пол:
|
|
« Ответ #23 : 06-11-2017 22:54 » |
|
Модификатор относится к объекту, у свободной функции нет объекта. Если в чем-то сомневаешься, поставь эксперимент. Это разрешит сомнения и закрепит знания на практике.
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
oktonion
Постоялец
Offline
|
|
« Ответ #24 : 06-11-2017 23:00 » |
|
RXL, так речи нигде и не шло о том что у свободной функции может быть модификатор const. А у member function как раз может быть. Какие тут сомнения то. =)
|
|
|
Записан
|
|
|
|
RXL
Технический
Администратор
Offline
Пол:
|
|
« Ответ #25 : 07-11-2017 00:18 » |
|
Вот ты сам же создаешь ненужную путаницу, а потом тебя еще понять нужно. Метод — устоявшийся термин, хорошо отличающий его от функции. Тут у нас человек, на плюсах не писавший. Не усложняй.
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
Aether
|
|
« Ответ #26 : 07-11-2017 19:12 » |
|
Ладно, я что-то соображаю. А может быть мне это только кажется. В целом, в некоторых задачах от "С++" есть плюс - избавление от ручного набора церемониальных свитков.
|
|
|
Записан
|
|
|
|
oktonion
Постоялец
Offline
|
|
« Ответ #27 : 07-11-2017 21:41 » |
|
Aether, у C++ много плюсов - целых два.
|
|
|
Записан
|
|
|
|
|