Diamentowy problem i dziedziczenie wirtualne

Język C++ oferuje możliwość dziedziczenia wielokrotnego ze wszystkimi wadami i zaletami tego rozwiązania. W przypadku dziedziczenia po kilku klasach mających wspólnego przodka może pojawić się problem z diamentem.

Błąd „base class is ambiguous” świadczy o tym, że dwie klasy, po których dziedziczy nasza „najmłodsza klasa” są potomkami wspólnego przodka. Jeśli obie te klasy dziedziczą po tym przodku niewirtualnie to otrzymamy wspomniany wcześniej błąd, ponieważ klasa dziadka zostanie dodana dwa razy, więc będzie zduplikowana. Rozwiązaniem problemu będzie zastosowane dziedziczenia wirtualnego w naszych dwóch klasach.

W moim projekcie dziedziczenie wirtualne zostało zastosowane przy dziedziczeniu podstawowej klasy pola – RootField. Klasa RootField przechowuje tylko główne, wspólne parametry pola takie jak szerokość i wysokość. Następnie mamy dwie klasy po niej dziedziczące – DrawingAreaField oraz BlocksDescriptionField. Jeden dostarcza metody dla obszaru rysowania (DrawingArea) a drugi dla opisów bloków (RowsDescription i ColumnsDescription). Klasa WholeField łączy te elementy w całość i pozwala odwoływać się do pola roboczego jako całości, np. z poziomu solvera.

class RootField : public QObject
{
	Q_OBJECT
public:
	RootField();
	RootField(size_t width, size_t height) : width(width), height(height) {}
	size_t getWidth() {return width;}
	size_t getHeight() {return height;}
signals:
	void dataChanged();
private:
	size_t width;
	size_t height;
};


class DrawingAreaField : public virtual RootField
{
public:
	virtual Pixel getPixel(AddressOnDrawingArea address) = 0;
	virtual void setPixel(Pixel pixel) = 0;
};


class BlocksDescriptionField : public virtual RootField
{
public:
	virtual BlockDescription getBlockDescription(AddressOnBlocksDescription address) = 0;
	virtual void updateBlockDescription(BlockDescription blockDescription) = 0;
	virtual void insertDescriptionBefore(BlockDescription blockDescription) = 0;
	virtual void addDescriptionAtEnd(BlockDescription blockDescription) = 0;
	virtual void deleteDescription(BlockDescription blockDescription) = 0;
	virtual size_t numberOfBlocksInColumn(size_t columnNumber) = 0;
	virtual size_t columnsDescriptionHeight() = 0;
	virtual bool isDefinedColumnDescriptionAt(size_t line, size_t count) = 0;
};


class WholeField : public DrawingAreaField, public BlocksDescriptionField, virtual public RootField
{
};


class WholeFieldImpl : public WholeField
{
public:
	WholeFieldImpl(size_t width, size_t height) : RootField(width, height)
	{
		array = ArrayOfPixels(width, height);
		columnsDescription = AllLinesDescription(AddressOnBlocksDescription::orientation::VERTICAL, width);
	}
	virtual Pixel getPixel(AddressOnDrawingArea address) override;
	virtual void setPixel(Pixel pixel) override;
	virtual BlockDescription getBlockDescription(AddressOnBlocksDescription address) override;
	virtual void updateBlockDescription(BlockDescription blockDescription) override;
	virtual void insertDescriptionBefore(BlockDescription blockDescription) override;
	virtual void addDescriptionAtEnd(BlockDescription blockDescription) override;
	virtual void deleteDescription(BlockDescription blockDescription) override;
	virtual size_t numberOfBlocksInColumn(size_t columnNumber) override;
	virtual size_t columnsDescriptionHeight() override;
	virtual bool isDefinedColumnDescriptionAt(size_t line, size_t count) override;
private:
	ArrayOfPixels array;
	AllLinesDescription columnsDescription;
};

Kolejnym problemem okazało się wywoływanie konstruktorów parametrycznych z klasy bazowej (dziedziczonej wirtualnie) za pomocą listy inicjalizacyjnej. Okazało się, że wywołanie konstruktora klasy bazowej (dziedziczonej wirtualnie) za pomocą listy inicjalizacyjnej musi znajdować się w najmłodszej klasie – w tej, której obiekt zostanie utworzony za pomocą konstruktora parametrycznego. W sytuacji gdy mamy hierarchię klas, np:

class A {
	int i;
public:
	A(int k) {}
};

class B : virtual public A {
public:
	B(int i) : A(i) {}
};

class C :   public B {
public:
	//C(int i) : B(i) {} // zostanie wywołany tylko konstruktor klasy B, konstruktor A(int k) nie zostanie wywołany, gdyż klasa A jest wirtualnie dziedziczona, zostanie wywołany konstruktor domyślny klasy A
	C(int i) : B(i), A(i) {}	// zostanę wywołane oba konstruktory parametryczne
};

Linki na temat:
multiple-inheritance-from-two-derived-classes
how-can-i-avoid-the-diamond-of-death-when-using-multiple-inheritance
Multiple_inheritance#The_diamond_problem
Virtual_inheritance

Wywoływanie konstruktorów z klasy wirtualnie dziedziczonej za pomocą listy inicjalizacyjnej.
order-of-constructor-call-in-virtual-inheritance

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *