Rokiのチラ裏

学生による学習のログ

多重継承でのEBO最適化

EBOの有効的な用いられ方としてコピー禁止を明快に表現する方法がある。

class noCopyable{
    noCopyable(const noCopyable&)=delete;
    noCopyable& operator=(const noCopyable&)=delete;
protected:
    noCopyable()=default;
    ~noCopyable()=default;
};

struct X:private noCopyable{};

noCopyableを継承する事によってコピー禁止を継承先へmix-inさせる。一般的なコンパイラであればデータメンバを持たないnoCopyableのような空のクラスはEBOと言われる最適化が成され余計なサイズを取らない。

std::cout<<sizeof(X)<<std::endl; // 1

しかし、このように多重継承をした場合どうなるだろう。

struct X:private noCopyable{};
struct Y:private noCopyable{};
struct Z:X,Y{};

#include<iostream>
int main()
{
    std::cout<<sizeof(Z)<<std::endl; // 2
}

これはEBOによる最適化を妨げる。異なるサブオブジェクトがそれぞれ同じ基底クラスを継承している時、双方共に異なるアドレスを持つことが規格によって定められているためである。異なるアドレスを持つためには、それぞれが最低限のサイズを持たなければならない。上記のコードで言えば、noCopyableをXとYがそれぞれ継承する時点でそれぞれが単体で最低限のサイズを持ち、それらをZが継承するのだから出力結果はごく自然と言える。しかしクラスのスタンスを明快にするためだけに継承の度に余計なサイズが毎回取られるというのは好ましくない。この問題は同じ型を継承する事にあるので、違う型にしてやれば良い。イディオムとしての共通性を保ちながら異なる型とするにはCRTPイディオムを用いる事で実現できる。

template<class>
class noCopyable{
    noCopyable(const noCopyable&)=delete;
    noCopyable& operator=(const noCopyable&)=delete;
protected:
    noCopyable()=default;
    ~noCopyable()=default;
};

struct X:private noCopyable<X>{};
struct Y:private noCopyable<Y>{};
struct Z:X,Y{};

#include<iostream>
int main()
{
    std::cout<<sizeof(Z)<<std::endl; // 1
}

CRTPによってX,Yはそれぞれ異なる型を継承するため、それに依存してZは異なるサブオブジェクトを持つようになる。つまりEBO最適化が可能となる。