メインコンテンツ

誤ったオブジェクト指向プログラミング

this ポインターの動的な型が不適切

説明

クラスのメンバー関数の呼び出しに対するこのチェックでは、呼び出しが有効であるかどうかを判別します。

メンバー関数の呼び出しは、次のような理由で無効になることがあります。

  • メンバー関数をその関数を指す関数ポインターを介して呼び出しています。しかし、関数と関数ポインターで引数または戻り値のデータ型が一致していません。

  • 純粋な virtual メンバー関数をクラスのコンストラクターまたはデストラクターから呼び出しています。

  • virtual メンバー関数の呼び出しで使用している this ポインターが正しくありません。関数の呼び出しに使用するオブジェクトのアドレスが this ポインターに格納されています。this ポインターが正しくない理由は次のとおりです。

    • オブジェクトを別のオブジェクトからのキャストによって取得しています。それらのオブジェクトが、関連のない 2 つのクラスのインスタンスになっています。

    • オブジェクトの配列を指すポインターに対してポインター演算を実行しています。しかし、そのポインター演算によってポインターが配列の範囲外になっています。ポインターをデリファレンスしたときに、有効なオブジェクトを指さなくなります。

すべて展開する

#include <iostream>
class myClass {
public: 
  void method() {}
};

void main() {
  myClass Obj;
  int (myClass::*methodPtr) (void) = (int (myClass::*) (void)) &myClass::method;
  int res = (Obj.*methodPtr)();
  std::cout << "Result = " << res;
}

この例では、ポインター methodPtr の戻り値の型は int ですが、戻り値の型 voidmyClass:method をポイントしています。したがって、ポインター methodPtr がデリファレンスされると、[誤ったオブジェクト指向プログラミング] チェックはレッド エラーを生成します。

#include <iostream>
class myClass {
public:
  void method() {}
};

void main() {
  myClass Obj;
  void (myClass::*methodPtr) (void) =  &myClass::method;
  methodPtr = 0;
  (Obj.*methodPtr)();
}

この例では、変数 methodPtr のデリファレンス時の値は NULL です。

class Shape {
public:
  Shape(Shape *myShape) {
    myShape->setShapeDimensions(0.0);
  }
  virtual void setShapeDimensions(double) = 0;
};

class Square: public Shape {
  double side;
public:
  Square():Shape(this) {
  }
  void setShapeDimensions(double);
};

void Square::setShapeDimensions(double val) {
  side=val;
}

void main() {
  Square sq;
  sq.setShapeDimensions(1.0);
}

この例では、派生クラス コンストラクター Square::Square が、this ポインターを使用して基底クラス コンストラクター Shape::Shape() を呼び出します。すると基底クラス コンストラクターは、this ポインターによって純粋なバーチャル関数 Shape::setShapeDimensions を呼び出します。コンストラクターからの純粋なバーチャル関数の呼び出しは定義されていないため、[誤ったオブジェクト指向プログラミング] チェックはレッド エラーを生成します。

#include <new>

class Foo {
public:
  void funcFoo() {}
};


class Bar {
public:
  virtual void funcBar() {}
};

void main() {
  Foo *FooPtr = new Foo;
  Bar *BarPtr = (Bar*)(void*)FooPtr;
  BarPtr->funcBar();
}

この例では、クラス Foo とクラス Bar に関連がありません。Foo* ポインターが Bar* ポインターにキャストされ、Bar* ポインターを使用してクラス Barvirtual メンバー関数が呼び出されると、[誤ったオブジェクト指向プログラミング] チェックはレッド エラーを生成します。

#include <new>
class Foo {
public:
    virtual void func() {}
};

void main() {
    Foo *FooPtr = new Foo[4];
    for(int i=0; i<=4; i++)
        FooPtr++;
    FooPtr->func();
    delete [] FooPtr;
}

この例では、ポインター FooPtr を使用して virtual メンバー関数 func() を呼び出す際に、そのポインターに割り当てられた範囲の外を指しています。有効なオブジェクトを指していません。したがって、[誤ったオブジェクト指向プログラミング] チェックはレッド エラーを生成します。

class Foo {
public:
  virtual int func() {
    return 1;
  }
};

class Ref {
public:
  Ref(Foo* foo) {
    foo->func();
  }
};

class Bar {
private:
  Ref m_ref;
  Foo m_Foo;
public:
  Bar() : m_ref(&m_Foo) {}
};

この例では、m_Foo の初期化前に、コンストラクター Bar::Bar()m_Foo のアドレスを使用してコンストラクター Ref::Ref() を呼び出します。&m_Foo を指すポインターを使って virtual メンバー関数 func が呼び出されると、[誤ったオブジェクト指向プログラミング] チェックがレッド エラーを生成します。

結果を再現するには、オプション [クラス] (-class-analyzer) を使用してクラス Bar のみを解析します。

#include <new>

class Foo {
public:
  virtual void funcFoo() {}
};


class Bar: public Foo {
public:
  void funcFoo() {}
};

void main() {
  Foo *FooPtr = new Foo;
  Bar *BarPtr = (Bar*)(void*)FooPtr;
  BarPtr->funcFoo();
}

この例では、funcFoo の派生クラス バージョンを呼び出そうとしていますが、使用しているコンパイラに応じて、基底クラス バージョンが呼び出されるか、セグメンテーション違反が発生します。

ポインター FooPtrFoo オブジェクトを指しています。キャストでは、Foo* ポインター FooPtr を誤って Bar* ポインター BarPtr に変換しようと試みます。BarPtr は引き続き基底 Foo オブジェクトを指しており、Bar::funcFoo にアクセスできません。

修正 — 基底クラス ポインターが派生クラス オブジェクトを直接指すようにする

C++ の多態性によって、クラス階層を走査して最上位の派生メンバー関数を指すことのできるポインターを定義できます。多態性を正しく実装するには、基底クラス ポインターから開始して、ポインターが派生クラス オブジェクトを指すようにします。

#include <new>

class Foo {
public:
  virtual void funcFoo() {}
};


class Bar: public Foo {
public:
  void funcFoo() {}
};

void main() {
  Foo *FooPtr = new Bar;
  FooPtr->funcFoo();
}

チェック情報

グループ: C++
言語: C++
頭字語: OOP