メインコンテンツ

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

CERT C++: OOP50-CPP

Do not invoke virtual functions from constructors or destructors

R2021a 以降

説明

ルール定義

Do not invoke virtual functions from constructors or destructors.1

Polyspace 実装

ルール チェッカーは、"コンストラクターおよびデストラクターからのバーチャル関数呼び出し" をチェックします。

すべて展開する

問題

コンストラクターおよびデストラクターからのバーチャル関数呼び出しは、コンストラクターまたはデストラクターで、予期しない結果になる可能性のあるバーチャル関数を呼び出す場合に発生します。

階層内のクラスのコンストラクターまたはデストラクターでバーチャル関数を呼び出すと、バーチャル関数のどのインスタンスを呼び出そうと意図しているのかが明確になりません。コンストラクターまたはデストラクターでのバーチャル関数の呼び出しは、バーチャル関数の最上位の派生オーバーライドではなく、現在実行中のクラスのバーチャル関数の実装に解決されます。

コンストラクターまたはデストラクターからバーチャル関数を呼び出しても、この欠陥が報告されないケースは 2 つあります。

  • 明示的な修飾子付き ID を使用してバーチャル関数を呼び出す場合。次のコードについて考えます。

    Base(){
    	Base::foo();
    }
    Base::foo の呼び出しで、関数の明示的な修飾子付き ID が使用されています。この呼び出しは、Base に属する foo の実装を呼び出すことを明示的に示しているため、このルールに準拠しています。

  • 呼び出す対象のバーチャル関数を現在実行中のクラスの 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() のみが呼び出されます。同様に、バーチャル関数 barBase のデストラクターで呼び出される時点で、派生クラス Derived は既に破棄されています。コンパイラは Derived::bar を呼び出すことができません。関数 Base::bar のみが呼び出されます。このコードの出力は次のとおりです。

Base Constructor
Base Destructor
以下のようにはしません。
Base Constructor
Derived constructor
Derived Constructor
Base Destructor
Derived クラスに属する d の部分は、割り当てられることも、割り当て解除されることもありません。この動作によってメモリ リークやセキュリティの脆弱性が生じる可能性があります。

修正方法

この問題を修正するには、コンストラクターおよびデストラクターでのバーチャル関数の呼び出しを避けます。一般的なコンストラクターやデストラクターのタスク (メモリの割り当てと割り当て解除、初期化、メッセージのログ記録など) では、階層内の各クラスに固有の関数を使用します。

例 — クラス固有のメモリ管理

#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 のコンストラクターおよびデストラクターでバーチャル関数を使用していることから、dObjDerived の部分は割り当てられることも、割り当て解除されることもありません。この予期しない動作は、メモリ リークやセキュリティの脆弱性につながる可能性があります。

修正 — クラス固有のメモリ管理

1 つの修正方法として、コンストラクターおよびデストラクターで一般的に行われるタスクでは、クラス固有の非バーチャル関数を使用します。このコードでは、割り当てタスクと割り当て解除タスクがクラス固有の非バーチャル関数によって実行されます。

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

チェック情報

グループ: Rule 09.オブジェクト指向プログラミング (OOP)

バージョン履歴

R2021a で導入


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.