ПроблемаВызов виртуальной функции из конструкторов и деструкторов происходит, когда вы вызываете виртуальные функции в конструкторе или деструкторе с возможно неожиданными результатами.
Когда вы вызываете виртуальные функции в конструкторе или деструкторе класса в иерархии, не ясно, какой образец виртуальной функции вы намерены вызвать. Вызовы виртуальных функций в конструкторе или деструкторе разрешаются к реализации виртуальной функции в классе выполняющегося в данного момента вместо самого производного переопределения.
Существует два случая, когда вызов виртуальных функций из конструктора или деструктора не поднимает этот дефект.
Когда вы используете явно сертифицированный идентификатор для вызова виртуальной функции. Рассмотрим этот код:
Вызов 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;
}