メインコンテンツ

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

不完全なクラス ポインターの変換または削除

不完全なクラスを指すポインターの削除またはキャスト

説明

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

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

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

リスク

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

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

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

修正方法

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

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

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

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

すべて展開する

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

この例では、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));
}

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

結果情報

グループ: オブジェクト指向
言語: C++
既定値: 手書きコードはオン、生成コードはオフ
コマンド ライン構文: INCOMPLETE_CLASS_PTR
影響度: Medium

バージョン履歴

R2018b で導入