exponenta event banner

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. Объектно-ориентированное программирование (ООП)
Представлен в R2021a

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

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

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