В производных классах техник FireSkill, WoodSkill и т.д. конструкторы по умолчанию закрыты, но базовый класс Skill объявлен как friend, что позволяет создавать объекты этих классов только внутри класса Skill.
Зачем такое ограничение? В данном примере объекты-то получатся взаимозаменяемыми, вне зависимости от того, созданы ли они через Skill(int) или непосредственно.
Если это ограничение убрать, и конструкторы сделать открытыми, friend не нужен.
Ну в таком случае вопрос вообще смысла не имеет.
Но если не обращать внимания на эту мелочь (:
Например, заменив delete pA на pA->A::~A(); а delete pB; на pb->B::~B();
Уточню: мы говорим о разрушении оператором delete объекта класса B через указатель на его базовый подобъект класса A, в случае если деструкторы A и B — невиртуальные.
Деструктор класса A будет вызван в любом случае. Вне зависимости от его виртуальности. И деструкторы всех предков A, если бы таковые были. Компилятор просто знает что их нужно вызвать, видя что delete применяется к A*. (Вас же не удивляет, что будь тут написано не
A * pA = new B; delete pA;
а
B * pB = new B; delete pB;
деструкторы будут вызваны сначала для B а потом и для A?)
Если следовать RAII, то каждый класс в иерархии должен в своем деструкторе освободить занятые именно этим классом ресурсы. Можно сделать это в отдельных функциях, вызываемых деструктором. Очевидно что таким функциям не нужно быть виртуальными. Виртуальным должен быть только деструктор того класса, к которому применяется delete.
Как бы там ни было, стандарт, в пункте 5.3.5:3 говорит о том, что если статический тип аргумента delete отличается от динамического типа объекта, а деструктор аргумента не является виртуальным, то поведение не определено.
В моих примерах не одни только методы. Я к тому, что данные бывают разными.
А кроме того, будь это правило действительно хорошим, оно было бы частью стандарта, вы не находите?
Происходит это потому, что удаление производится через указатель на базовый класс и для вызова деструктора компилятор использует раннее связывание. Деструктор базового класса не может вызвать деструктор производного, потому что он о нем ничего не знает. В итоге часть памяти, выделенная под производный класс, безвозвратно теряется.
Ну в вашем-то примере никакой утечки не будет. Механизм описан верно, но конечный вывод не совсем корректен: пока классы-наследники не выделяют ресурсов, на освобождение которых они могли бы рассчитывать при вызове свооего деструктора, всё будет в порядке. Такими ресурсами могут быть как динамическая память, так и внешние объекты: файлы, таймеры,… А память под объект класса будет выделена одним куском, одним куском она и освободится, вне зависимости от того, через какой указатель.
Удобство cinoptions в том, что там есть по управляющей конструкции практически на любой элемент форматирования, упоминаемый в большинстве стандартов кодирования. И подправить это выражение достаточно просто. А кроме того, indentexpr для С и С++ выполняется с помощью cindent, согласно $VIMRUNTIME/indent/{c,cpp}.vim
Еще более life-changing будет cindent, автоматически применяющий выбранный с помощью cinoptions стиль кодирования, как при вводе, так и при переформатировании командой =
proc ctoh
mov dword ptr cs:[string],'0000' ; а это и есть собственно декодер аудио
mov dword ptr cs:[string+4],'0o000'
mov cx,0
ctoh1: cmp byte ptr [si],30h \ цикл поиска последовательности десятичных цифр
jc ctoh2 | занятно, что нет ограничения на максимальную длину последовательности
cmp byte ptr [si],3ah |
jnc ctoh2 |
inc si | si -- текущая рассматриваемая цифра
inc cx | cx -- текущая длина строки
jmp ctoh1 /
ctoh2:
mov di,offset cs:string+7 \ копирование найденной строки цифр в буфер string
push si | начиная с конца
dec si |
std | задом наперед
rep movsb | buffer overflow, если символов было больше 8
pop si |
inc si /
mov bx,offset cs:string+7 \ подготовка к получению числового значения десятичного числа записанного строкой
mov edx,1 | текущий вес младшего разряда
mov cx,8 | количество разрядов
mov dword ptr cs:[len],0 / обнулили значение результата
ctoh_loop1: \ цикл умножения цифр числа на веса их разрядов
mov eax,0 |
mov al,byte ptr cs:[bx] | достать цифру
sub al,30h | перевести символ '0'-'9' в число 0-9
imul eax,edx | умножить на вес текущего разряда
add dword ptr cs:[len],eax | добавить к результату
imul edx,10 | увеличить вес разряда
dec bx | перейти к предыдущей цифре
loop ctoh_loop1 /
ret
ctoh endp
Зачем такое ограничение? В данном примере объекты-то получатся взаимозаменяемыми, вне зависимости от того, созданы ли они через Skill(int) или непосредственно.
Если это ограничение убрать, и конструкторы сделать открытыми, friend не нужен.
Но если не обращать внимания на эту мелочь (:
Например, заменив delete pA на pA->A::~A(); а delete pB; на pb->B::~B();
Деструктор класса A будет вызван в любом случае. Вне зависимости от его виртуальности. И деструкторы всех предков A, если бы таковые были. Компилятор просто знает что их нужно вызвать, видя что delete применяется к A*. (Вас же не удивляет, что будь тут написано не
а
деструкторы будут вызваны сначала для B а потом и для A?)
В чем теперь ваш вопрос?
И кроме того, такой delete — это неопределенное поведение, как сказано ниже.
А кроме того, будь это правило действительно хорошим, оно было бы частью стандарта, вы не находите?
Ну в вашем-то примере никакой утечки не будет. Механизм описан верно, но конечный вывод не совсем корректен: пока классы-наследники не выделяют ресурсов, на освобождение которых они могли бы рассчитывать при вызове свооего деструктора, всё будет в порядке. Такими ресурсами могут быть как динамическая память, так и внешние объекты: файлы, таймеры,… А память под объект класса будет выделена одним куском, одним куском она и освободится, вне зависимости от того, через какой указатель.
Например:
— не будет утечек.
— будут утечки.
Резюме: можно и это выкинуть.