1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

オブジェクトスライシングでオーバーライドが失敗した話

Posted at

はじめに

ポリモーフィズムにおいて、思った通りに動作しないことがありました。

派生クラスがいくつかあって、そのうち 1 つだけ処理が違うために virtual 関数をオーバーライドしていました。

ただ、実際に手元で扱っているのは Get 関数から返ってくる 基底クラスの配列です。

この状態で、配列の中身が派生クラスの場合、オーバーライドした関数が正しく呼ばれるのかどうかを確認してみました。

確認したことや整理した内容を、備忘録としてまとめておきます。

結論

  • 値で保持するとオブジェクトスライシングが発生し、オーバーライドは効かない
  • ポインタ(生ポインタ or スマートポインタ)で保持するとオーバーライドが正しく動く

今回の調査で、この現象がオブジェクトスライシングと呼ばれるものだということを知りました。

DerivedBase へコピーされる際は、派生クラス部分が切り捨てられるそうです。

デモコード

#include <iostream>
#include <memory>
#include <vector>

using namespace std;

// 基底クラス
class Base {
public:
    virtual void virtualFunc() { cout << "Base::virtualFunc\n"; }

    virtual ~Base() = default;
};

// 基底のまま使う派生クラス
class DerivedDefaultA : public Base { };
class DerivedDefaultB : public Base { };
// 例外的に振る舞いを変える派生クラス
class DerivedCustom : public Base {
public:
    void virtualFunc() override { cout << "DerivedCustom::virtualFunc\n"; }
};

// 値で返す
vector<Base> GetListByValue() {
    vector<Base> v;
    v.emplace_back(DerivedDefaultA());
    v.emplace_back(DerivedDefaultB());
    v.emplace_back(DerivedCustom());
    return v;
}

// 生ポインタで返す
vector<Base*> GetListByRawPointer() {
    vector<Base*> v;
    v.push_back(new DerivedDefaultA());
    v.push_back(new DerivedDefaultB());
    v.push_back(new DerivedCustom());
    return v;
}

// スマートポインタで返す
vector<unique_ptr<Base>> GetListByUniquePtr() {
    vector<unique_ptr<Base>> v;
    v.emplace_back(make_unique<DerivedDefaultA>());
    v.emplace_back(make_unique<DerivedDefaultB>());
    v.emplace_back(make_unique<DerivedCustom>());
    return v;
}

int main() {
    cout << "-- GetListByValue() --\n";
    auto listVal = GetListByValue();
    for (auto &b : listVal) {
        b.virtualFunc();
    }

    cout << "\n-- GetListByRawPointer() --\n";
    auto listRaw = GetListByRawPointer();
    for (auto p : listRaw) {
        p->virtualFunc();
    }
    for (auto p : listRaw) delete p;

    cout << "\n-- GetListByUniquePtr() --\n";
    auto listPtr = GetListByUniquePtr();
    for (auto &p : listPtr) {
        p->virtualFunc();
    }

    return 0;
}

実行結果

値で返した場合だけ、DerivedCustom のオーバーライドが呼ばれていないことが確認できます。

-- GetListByValue() --
Base::virtualFunc
Base::virtualFunc
Base::virtualFunc

-- GetListByRawPointer() --
Base::virtualFunc
Base::virtualFunc
DerivedCustom::virtualFunc

-- GetListByUniquePtr() --
Base::virtualFunc
Base::virtualFunc
DerivedCustom::virtualFunc
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?