ПроблемаВызов виртуальной функции из конструкторов и деструкторов происходит при вызове виртуальных функций в конструкторе или деструкторе с возможными неожиданными результатами.
При вызове виртуальных функций в конструкторе или деструкторе класса в иерархии не ясно, какой экземпляр виртуальной функции требуется вызвать. Вызовы виртуальных функций в конструкторе или деструкторе разрешаются в реализацию виртуальной функции в исполняемом в данный момент классе вместо наиболее производного переопределения.
Существует два случая, когда вызов виртуальных функций из конструктора или деструктора не вызывает этот дефект.
Если для вызова виртуальной функции используется явно определенный идентификатор. Рассмотрим этот код:
Вызов Base::foo использует явно определенный идентификатор функции. Этот вызов соответствует этому правилу, поскольку он явно указывает, что реализация foo принадлежность к Base вызывается.При указании вызываемой виртуальной функции как final в исполняемом в данный момент классе. Рассмотрим этот код:
Base(){
foo();
}
//...
void foo() override final{
//...
} В этом случае вызов foo подразумевает вызов Base::foo потому что функция указана как окончательное переопределение.
РискПри вызове виртуальной функции требуется, чтобы компилятор разрешил вызов наиболее производного переопределения виртуальной функции во время выполнения. В отличие от других функций, вызовы виртуальных функций в конструкторах и деструкторах разрешаются по-разному, что приводит к неожиданному поведению. Вызов виртуальных функций в конструкторе и деструкторах может привести к неопределенному поведению, что приведет к утечке памяти и уязвимостям безопасности. Рассмотрим этот код:
#include <iostream>
class Base
{
public:
Base() { foo(); } //Noncompliant
~Base(){bar();} //Noncompliant
virtual void foo() {
std::cout<<"Base Constructor\n";
}
virtual void bar(){
std::cout<<"Base Destructor\n";
}
};
class Derived : public Base
{
public:
Derived() : Base() {}
~Derived() = default;
virtual void foo() {
std::cout<<"Derived constructor\n";
}
virtual void bar() {
std::cout<<"Derived Constructor\n";
}
};
int main(){
Derived d;
return 1;
}Конструктор d вызывает конструктор для Base класс, который вызывает виртуальную функцию foo. Поскольку производный класс еще не создан, компилятор не может вызвать Derived::foo. Только функция Base::foo() вызывается. Аналогично, когда виртуальная функция bar вызывается в деструкторе Base, производный класс Derived уже уничтожен. Компилятор не может вызвать Derived::bar. Только функция Base::bar вызывается. Выходные данные этого кода:
Base Constructor
Base Destructor
вместо:
Base Constructor
Derived constructor
Derived Constructor
Base Destructor
Часть
d принадлежность к классу
Derived не выделяется и не освобождается. Такое поведение может привести к утечке памяти или уязвимости системы безопасности.
ЗафиксироватьЧтобы устранить эту проблему, избегайте вызова виртуальных функций в конструкторах и деструкторах. Для типичных задач конструктора или деструктора, таких как выделение и освобождение памяти, инициализация или ведение журнала сообщений, используйте функции, специфичные для каждого класса в иерархии.
Пример - Управление памятью для конкретного класса
#include <iostream>
class Base {
public:
Base()
{
allocator(); //Noncompliant
}
virtual ~Base()
{
deallocator(); //Noncompliant
}
virtual void allocator(){
//...
}
virtual void deallocator(){
//...
}
};
class Derived : public Base {
public:
Derived() : Base() {}
virtual ~Derived() = default;
protected:
void allocator() override
{
Base::allocator();
// Get derived resources...
}
void deallocator() override
{
// Release derived resources...
Base::deallocator();
}
};
int main(){
Derived dObj;
//...
return 1;
}В этом примере код пытается управлять памятью для конкретного класса, реализуя функции allocator и deallocator как виртуальный. Вызов этих функций не позволяет получить наиболее производное переопределение.
Во время строительства Derived объект dObj, только функция Base::allocator() вызывается. Потому что Derived класс еще не создан, функция Derived::allocator не вызывается.
При уничтожении dObj, только функция Base::deallocator вызывается, так как класс Derived уже уничтожен.
Из-за использования виртуальных функций в конструкторе и деструкторе dObj, Derived часть dObj не выделяется и не освобождается. Такое поведение является неожиданным и может привести к утечке памяти и уязвимостям системы безопасности.
Коррекция - управление памятью для конкретного классаОдной из возможных корректировок является использование зависящих от класса невиртуальных функций для задач, которые обычно выполняются в конструкторах и деструкторах. В этом коде задачи распределения и отмены назначения выполняются зависящими от класса невиртуальными функциями.
#include <iostream>
class Base {
public:
Base()
{
allocator_base();
}
virtual ~Base()
{
deallocator_base();
}
protected:
void allocator_base(){
// Allocate base resources
}
void deallocator_base(){
// Deallocate base resources
}
};
class Derived : public Base {
public:
Derived(){
allocator_derived();
}
virtual ~Derived(){
deallocator_derived();
}
protected:
void allocator_derived()
{
// Allocate derived resources...
}
void deallocator_derived()
{
// Deallocate derived resources...
}
};
int main(){
Derived dObj;
//...
return 1;
}