CERT C++: OOP50-CPP

Не вызывайте виртуальные функции от конструкторов или деструкторов

Описание

Определение правила

Не вызывайте виртуальные функции от конструкторов или деструкторов.[1]

Реализация Polyspace

Эта проверка проверяет вызов виртуальной функции из конструкторов и деструкторов.

Примеры

расширить все

Проблема

Вызов виртуальной функции из конструкторов и деструкторов происходит, когда вы вызываете виртуальные функции в конструкторе или деструкторе с возможно неожиданными результатами.

Когда вы вызываете виртуальные функции в конструкторе или деструкторе класса в иерархии, не ясно, какой образец виртуальной функции вы намерены вызвать. Вызовы виртуальных функций в конструкторе или деструкторе разрешаются к реализации виртуальной функции в классе выполняющегося в данного момента вместо самого производного переопределения.

Существует два случая, когда вызов виртуальных функций из конструктора или деструктора не поднимает этот дефект.

  • Когда вы используете явно сертифицированный идентификатор для вызова виртуальной функции. Рассмотрим этот код:

    Base(){
    	Base::foo();
    }
    Вызов 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;
}

Проверяйте информацию

Группа: Правило 09. Объектно-ориентированное программирование (OOP)
Введенный в R2021a

[1] Это программное обеспечение было создано MathWorks, включающее фрагменты: «Сайт SEI CERT-C», © 2017 Университет Карнеги Меллон, Веб-сайт SEI CERT-C + + © 2017 Университет Карнеги Меллон, "Стандарт кодирования SEI CERT C - Правила разработки безопасных, Надежные и безопасные системы - 2016 Edition ", © 2016 Университет Карнеги Меллон, и "Стандарт кодирования SEI CERT C++ - Правила разработки безопасных, Надежные и безопасные системы в C++ - 2016 Edition "© 2016 Университет Карнеги Меллон, с специального разрешения от его Института программной инженерии.

ЛЮБОЙ МАТЕРИАЛ УНИВЕРСИТЕТА КАРНЕГИ МЕЛЛОН И/ИЛИ ЕГО ИНЖЕНЕРНОГО ИНСТИТУТА ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ, СОДЕРЖАЩИЙСЯ В НАСТОЯЩЕМ ДОКУМЕНТЕ, ПОСТАВЛЯЕТСЯ НА БАЗИСЕ «КАК ЕСТЬ». УНИВЕРСИТЕТ КАРНЕГИ МЕЛЛОН НЕ ДАЕТ НИКАКИХ ГАРАНТИЙ, ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, В ОТНОШЕНИИ ЛЮБОГО ВОПРОСА, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ, ГАРАНТИЮ ПРИГОДНОСТИ ДЛЯ ЦЕЛЕЙ ИЛИ КОММЕРЧЕСКОЙ ВЫГОДЫ, ИСКЛЮЧИТЕЛЬНОСТИ, ИЛИ УНИВЕРСИТЕТ КАРНЕГИ МЕЛЛОН НЕ ДАЕТ НИКАКИХ ГАРАНТИЙ В ОТНОШЕНИИ СВОБОДЫ ОТ ПАТЕНТА, ТОВАРНОГО ЗНАКА ИЛИ НАРУШЕНИЯ АВТОРСКИХ ПРАВ.

Это программное обеспечение и связанная с ним документация не были рассмотрены и не одобрены Университетом Карнеги-Меллон или его Институтом программной инженерии.