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