CERT C++: EXP57-CPP

Не приводите и не удаляйте указатели на неполные классы

Описание

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

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

Реализация Polyspace

Эта проверка проверяет преобразование или удаление неполного указателя класса.

Примеры

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

Проблема

Преобразование или удаление неполного указателя класса происходит при удалении или приведении к указателю на неполный класс. Неполный класс является классом, определение которого не отображается в точке, где используется класс.

Например, определение Body классов не отображается, когда delete вызывается оператор для указателя на Body:

class Handle {
  class Body *impl;  
public:
  ~Handle() { delete impl; }
  // ...
};

Риск

При удалении указателя на неполный класс невозможно вызвать нетривиальный деструктор, который мог бы иметь класс. Если деструктор выполняет операции очистки, такие как удаление памяти, эти действия не выполняются.

Аналогичная проблема возникает, например, когда вы понижаете значение до указателя на неполный класс (понижение значения - это приведение значения от указателя на базовый класс к указателю на производный класс). На точку даункастинга связь между основой и производным классом не известна. В частности, если производный класс наследует от нескольких классов, в точке нисходящего преобразования эта информация не доступна. Понижающее значение не может вносить необходимые корректировки для нескольких наследований, и полученный указатель не может быть удален.

Аналогичный оператор может быть сделан для апкастинга (приведение из указателя в производный класс в указатель на базовый класс).

Зафиксировать

Когда вы удаляете или понижаете значение на указатель на класс, убедитесь, что определение класса отображается.

Также можно выполнить одно из следующих действий:

  • Вместо обычного указателя используйте std::shared_ptr type, чтобы указать на неполный класс.

  • При понижении, убедитесь, что результат действителен. Запись кода обработки ошибок для недопустимых результатов.

Пример - Удаление указателя на неполный класс
class Handle {
  class Body *impl;  
public:
  ~Handle() { delete impl; } 
  // ...
};

В этом примере определение класса Body не отображается, когда указатель на Body удаляется.

Коррекция - Определение класса перед удалением

Одной из возможных коррекций является обеспечение видимости определения класса при удалении указателя на класс.

class Handle {
  class Body *impl;  
public:
  ~Handle();
  // ...
};
 
// Elsewhere
class Body { /* ... */ };
  
Handle::~Handle() {
  delete impl;
}
Коррекция - Использование std::shared_ptr

Другой возможной коррекцией является использование std::shared_ptr вместо обычного указателя введите.

#include <memory>
  
class Handle {
  std::shared_ptr<class Body> impl;
  public:
    Handle();
    ~Handle() {}
    // ...
};
Пример - Преобразование вниз в указатель на неполный класс

File1.h:

">class Base {
protected:
  double var;
public:
  Base() : var(1.0) {}
  virtual void do_something();
  virtual ~Base();
};

File2.h:

">void funcprint(class Derived *);
class Base *get_derived(); 

File1.cpp:

#include "File1.h"
#include "File2.h"
 
void getandprint() {
  Base *v = get_derived();
  funcprint(reinterpret_cast<class Derived *>(v));
}

File2.cpp:

#include "File2.h"
#include "File1.h"
#include <iostream>
 
class Base2 {
protected:
  short var2;
public:
  Base2() : var2(12) {}
};
 
class Derived : public Base2, public Base {
  float var_derived;
public:
    Derived() : Base2(), Base(), var_derived(1.2f) {}
    void do_something()
    {
        std::cout << "var_derived: "
                  << var_derived << ", var : " << var
                  << ", var2: " << var2 << std::endl;
    }
 };
 
void funcprint(Derived *d) {
  d->do_something();
}
 
Base *get_derived() {
  return new Derived;
}

В этом примере определение класса Derived не отображается в File1.cpp когда a Base* указатель на нисходящий вызов на Derived* указатель.

В File2.cpp, классы Derived происходит из двух классов, Base и Base2. Эта информация о нескольких наследованиях недоступна в точке понижений в File1.cpp. Результат нисходящего преобразования передается в функцию funcprint и высмеивается в теле funcprint. Поскольку преобразование вниз было выполнено с неполной информацией, dereference может быть недопустимым.

Коррекция - задайте класс перед понижением значения

Одной из возможных коррекций является определение Derived классов перед понижением Base* указатель на Derived* указатель.

В этом исправленном примере преобразование вниз выполняется в File2.cpp в теле funcprint в точке, где определение класса Derived отображается. Преобразование вниз не выполняется в File1.cpp где определение Derived не отображается. Изменения предыдущего неправильного примера подсвечиваются.

File1.h:

class Base {
protected:
  double var;
public:
  Base() : var(1.0) {}
  virtual void do_something();
  virtual ~Base();
};

File2.h:

void funcprint(class Base *);
class Base *get_derived(); 

File1.cpp:

#include "File1.h"
#include "File2.h"
 
void getandprint() {
  Base *v = get_derived();
  funcprint(v);
}

File2.cpp:

#include "File2_corr.h"
#include "File1_corr.h"
#include <iostream>
 
class Base2 {
protected:
  short var2;
public:
  Base2() : var2(12) {}
};
 
class Derived : public Base2, public Base {
  float var_derived;

public:
    Derived() : Base2(), Base(), var_derived(1.2f) {}
    void do_something()
    {
        std::cout << "var_derived: "
                  << var_derived << ", var : " << var
                  << ", var2: " << var2 << std::endl;
    }
};
 
void funcprint(Base *d) {
  Derived *temp = dynamic_cast<Derived*>(d);
  if(temp)  {
     d->do_something();
  }
  else {
      //Handle error
  }
}
 
Base *get_derived() {
  return new Derived;
}

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

Группа: 02. Выражения (EXP)
Введенный в R2019a

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

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

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