メインコンテンツ

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

CERT C++: OOP51-CPP

Do not slice derived objects

説明

ルール定義

派生オブジェクトをスライスしないようにします。1

Polyspace 実装

ルール チェッカーは、"オブジェクトのスライス" をチェックします。

すべて展開する

問題

オブジェクトのスライスは、派生クラス オブジェクトを関数に値渡しする一方で、その関数がパラメーターとして基底クラス オブジェクトを必要とする場合に発生します。

リスク

派生クラス オブジェクトを関数に "値渡し" する場合は、派生クラスのコピー コンストラクターが呼び出されるものと想定されます。関数で必要とされるのがパラメーターとしての基底クラス オブジェクトであった場合、

  • 基底クラスのコピー コンストラクターが呼び出されます。

  • 関数本体で、パラメーターは基底クラス オブジェクトとみなされます。

C++ では、クラスの virtual メソッドがオブジェクトの実際のタイプに応じて実行時に解決されます。オブジェクトのスライスのため、virtual メソッドの不適切な実装が呼び出される場合があります。たとえば、基底クラスには virtual メソッドが含まれ、派生クラスにはそのメソッドの実装が含まれているとします。virtual メソッドを関数本体から呼び出すと、関数に派生クラスのオブジェクトを渡しても、基底クラスのメソッドが呼び出されます。

修正方法

1 つの修正方法として、オブジェクトを参照かポインターで渡します。参照かポインターで渡せば、コピー コンストラクターの呼び出しは発生しません。オブジェクトが変更されないようにするには、関数パラメーターに const 修飾子を使用します。

別の修正方法として、関数を、派生クラス オブジェクトをパラメーターとして受け取る別の関数でオーバーロードします。

例 - オブジェクトのスライスの原因となる関数呼び出し
#include <iostream>

class Base {
public:
    explicit Base(int b) {
    	_b = b;
    }
    virtual ~Base() {} 
    virtual int update() const;
protected:
    int _b;
};


class Derived: public Base {
public:
    explicit Derived(int b):Base(b) {}
    int update() const;
};

//Class methods definition

int Base::update() const {
    return (_b + 1);
}

int Derived::update() const {
    return (_b -1);
}


//Other function definitions
void funcPassByValue(const Base bObj) {
    std::cout << "Updated _b=" << bObj.update() << std::endl;
}

int main() {
    Derived dObj(0);
    funcPassByValue(dObj);       //Function call slices object //Noncompliant
    return 0;
 }

この例では、呼び出し funcPassByValue(dObj) の出力は Updated _b=1 となり、予想された Updated _b=-1 にはなりません。funcPassByValue では Base オブジェクト パラメーターが必要とされるため、Base クラスのコピー コンストラクターが呼び出されます。

したがって、Derived オブジェクト dObj が渡されても、関数 funcPassByValue はそのパラメーター bBase オブジェクトとして扱います。呼び出されるのは Base::update() であり、Derived::update() ではありません。

修正 — オブジェクトを参照またはポインターで渡す

1 つの修正方法として、Derived オブジェクト dObj を参照またはポインターで渡します。次の修正例において、funcPassByReferencefuncPassByPointer は前述の例の funcPassByValue と同じオブジェクティブをもちます。しかし、funcPassByReference では Base オブジェクトへの参照を必要としており、funcPassByPointer では Base オブジェクトを指すポインターを必要としています。

Derived オブジェクト d をポインターまたは参照で渡すと、オブジェクトはスライスされません。funcPassByReference(dObj) および funcPassByPointer(&dObj) の呼び出しにより、予想どおりの結果 Updated _b=-1 が生成されます。

#include <iostream>

class Base {
public:
    explicit Base(int b) {
    	_b = b;
    }
    virtual ~Base() {}
    virtual int update() const;
protected:
    int _b;
};


class Derived: public Base {
public:
    explicit Derived(int b):Base(b) {}
    int update() const;
};

//Class methods definition

int Base::update() const {
    return (_b + 1);
}

int Derived::update() const {
    return (_b -1);
}


//Other function definitions
void funcPassByReference(const Base& bRef) {
    std::cout << "Updated _b=" << bRef.update() << std::endl;
}

void funcPassByPointer(const Base* bPtr) {
    std::cout << "Updated _b=" << bPtr->update() << std::endl;
}

int main() {
    Derived dObj(0);
    funcPassByReference(dObj);       //Function call does not slice object
    funcPassByPointer(&dObj);       //Function call does not slice object
    return 0;
 }

メモ

値渡しの場合、オブジェクトのコピーが作成されるため、元のオブジェクトは変更されません。参照かポインターで渡すと、オブジェクトには変更に対して脆弱になります。元のオブジェクトが変更されることに懸念がある場合は、前述の例のように、関数パラメーターに const 修飾子を追加します。

チェック情報

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

バージョン履歴

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.