メインコンテンツ

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

オブジェクトのスライス

派生クラス オブジェクトが基底クラス パラメーターをもつ関数に値渡しされる

説明

この欠陥は、派生クラス オブジェクトを関数に値で渡したが、その関数がパラメーターとして基底クラス オブジェクトを予期していた場合に発生します。

リスク

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

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

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

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
    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 修飾子を追加します。

結果情報

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

バージョン履歴

R2015b で導入