CERT C++: OOP50-CPP

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

Описание

Управляйте определением

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

Реализация Polyspace

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

Примеры

развернуть все

Проблема

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

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

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

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

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

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

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

[1]  Это программное обеспечение было создано MathWorks, включающим фрагменты: “Веб-сайт SEI CERT-C”, © 2017 Carnegie Mellon University, веб-сайт SEI CERT-C © 2017 Carnegie Mellon University”, CERT SEI C Кодирование Стандарта – Правил для Разработки безопасных, Надежных и Защищенных систем – 2 016 Выпусков”, © 2016 Carnegie Mellon University, and “CERT SEI Стандарт Кодирования C++ – Правил для Разработки безопасных, Надежных и Защищенных систем на C++ – 2 016 Выпусков” © 2016 Carnegie Mellon University, со специальным разрешением от его Института программной инженерии.

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

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