メインコンテンツ

このページの内容は最新ではありません。最新版の英語を参照するには、ここをクリックします。

CERT C++: EXP57-CPP

Do not cast or delete pointers to incomplete classes

説明

ルール定義

ポインターを不完全なクラスにキャストしたり、不完全なクラスのポインターを削除したりしないようにします。1

Polyspace 実装

ルール チェッカーは、"不完全なクラス ポインターの変換または削除" をチェックします。

すべて展開する

問題

不完全なクラス ポインターの変換または削除は、不完全なクラスを指すポインターを削除するか、そのようなポインターにキャストした場合に発生します。不完全なクラスとは、そのクラスが使用される時点でその定義が可視になっていないポインターのことです。

たとえば、クラス Body の定義は、delete 演算子が Body のポインターに対して呼び出されたときには可視ではありません。

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

リスク

不完全なクラスを指すポインターを削除すると、そのクラスに含まれている可能性のある非トリビアル デストラクターを呼び出せなくなります。デストラクターでメモリ割り当て解除などのクリーンアップ アクティビティを実行している場合、これらのアクティビティが発生しません。

同様の問題は、たとえば、不完全なクラスを指すポインターにダウンキャストする場合に発生します (ダウンキャストとは、基底クラスを指すポインターから派生クラスを指すポインターにキャストすることです)。ダウンキャストの時点では、基底クラスと派生クラスの関係は不明です。特に、派生クラスが複数のクラスから継承している場合、ダウンキャストの時点ではこの情報を利用できません。ダウンキャストでは多重継承に必要な調整を加えることができず、その結果のポインターはデリファレンスできません。

アップキャスト (派生クラスを指すポインターから基底クラスを指すポインターにキャストすること) についても同様の説明ができます。

修正方法

クラスを指すポインターを削除したり、そのようなポインターにダウンキャストしたりする場合、クラス定義が可視であることを確認します。

あるいは、次のいずれかの操作を実行できます。

  • 通常のポインターではなく、std::shared_ptr 型を使用して不完全なクラスを指す。

  • ダウンキャストするときに、その結果が有効であることを確認する。無効な結果についてはエラー処理コードを作成する。

例 - 不完全なクラスを指すポインターの定義
class Handle {
  class Body *impl;  
public:
  ~Handle() { delete impl; } //Noncompliant
  // ...
};

この例では、Body を指すポインターが削除されるとき、クラス Body の定義は可視ではありません。

修正 — 削除の前にクラスを定義

1 つの修正方法として、クラスを指すポインターを削除するときにクラス定義が可視になるようにします。

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)); //Noncompliant
}

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;
}

この例では、Base* ポインターを Derived* ポインターにダウンキャストするときに、クラス Derived の定義が File1.cpp 内で可視になっていません。

File2.cpp では、クラス Derived は 2 つのクラス Base および Base2 から派生しています。File1.cpp では多重継承に関する情報がダウンキャストの時点では利用できません。ダウンキャストの結果は関数 funcprint に渡され、funcprint の本体内でデリファレンスされます。ダウンキャストが不完全な情報を使用して行われたため、デリファレンスは無効になる可能性があります。

修正 — ダウンキャストの前にクラスを定義

1 つの修正方法として、Base* ポインターを Derived* ポインターにダウンキャストする前に、クラス Derived を定義します。

この修正例の File2.cpp では、クラス Derived の定義が可視になっている時点で、funcprint の本体内でダウンキャストが行われています。Derived の定義が可視になっていない File1.cpp では、ダウンキャストが行われていません。前の不適切な例からの変更が強調表示されています。

File1_corr.h:

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

File2_corr.h:

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

File1.cpp:

#include "File1_corr.h"
#include "File2_corr.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 This software has been created by MathWorks incorporating portions of: the “SEI CERT-C Website,” © 2017 Carnegie Mellon University, the SEI CERT-C++ Web site © 2017 Carnegie Mellon University, ”SEI CERT C Coding Standard – Rules for Developing safe, Reliable and Secure systems – 2016 Edition,” © 2016 Carnegie Mellon University, and “SEI CERT C++ Coding Standard – Rules for Developing safe, Reliable and Secure systems in C++ – 2016 Edition” © 2016 Carnegie Mellon University, with special permission from its Software Engineering Institute.

ANY MATERIAL OF CARNEGIE MELLON UNIVERSITY AND/OR ITS SOFTWARE ENGINEERING INSTITUTE CONTAINED HEREIN IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.

This software and associated documentation has not been reviewed nor is it endorsed by Carnegie Mellon University or its Software Engineering Institute.