Наследование

Ü поддерживает наследование для классов. Наследование позволяет создавать классы, частично заимствующие состав и поведение классов-предков. Наследование также позволяет реализовать динамический полиморфизм.

В наследовании могут принимать участие только полиморфные классы. Полиморфными являются классы, объявленные с использованием ключевых слов polymorph, abstract или inteface, или классы, имеющие предков.

class A polymorph // Класс, от которого можно наследоваться.
{}

class B interface // Интерфейс. От него можно наследоваться, но сам он имеет ряд ограничений на своё содержимое.
{}

class C abstract // Тоже полиморфный класс, но в котором разрешены нереализованные виртуальные функции.
{}

class D : C // Полиморфный класс, т. к. имеет предка.
{}

class E : B // Полиморфный класс с предком-интерфейсом.
{}

class F : A, B // Полиморфный класс с двумя предками, один из которых - интерфейс.
{}

class G final : F // Полиморфный класс, от которого нельзя унаследоваться.
{}

Правила наследования

Класс может иметь не более одного неинтерфейсного предка и любое количество интерфейсных предков. Неинтерфейсный предковый класс считается базой класса. Обратиться к нему можно с использованием ключевого слова base. base - это this, но преобразованный в тип базового класса.

Существует ряд особых видов полиморфных классов:

  • interface класс, это такой полиморфный класс, у которого не может быть базового класса, полей, виртуальных методов с реализацией.
  • final класс, это такой полиморфный класс, от которого уже нельзя унаследоваться.
  • abstract класс, это такой полиморфный класс, у которого могут быть нереализованные виртуальные методы. У полиморфных классов, не помеченных как abstract или interface все виртуальные методы должны иметь реализацию.

Ограничения абстрактных классов и интерфейсов

Абстрактные классы и интерфейсы не являются полноценными классами, они имеют ряд ограничений:

  • Нельзя создать экземпляр абстрактного класса или интерфейса, вместо этого можно только унаследоваться от них и создать экземпляр класса-потомка.
  • Интерфейсы не могут иметь конструкторов. Они им не нужны, т. к. у них нету полей, которые необходимо было бы инициализировать в конструкторах.
  • Абстрактные классы могут иметь конструкторы, но в них не доступен this, что означает, что можно обращаться только к конкретным полям по имени и нельзя вызывать никакие методы.
  • Интерфейсы и абстрактные классы могут иметь деструкторы, но в них this также не доступен.

Преобразование ссылок

Ссылка на класс-потомок может быть явно или неявно преобразована в ссылку на класс-предок. Неявные преобразования возможны:

  • При инициализации ссылочной переменной или ссылочного поля
  • При передаче аргумента в функцию
  • При возврате значения из функции

Состав наследуемого содержимого

Класс-потомок наследует от класса-предка public и protected члены. private члены класса не наследуются. Не наследуются конструкторы, деструкторы, операторы присваивания, операторы сравнения (==, <=>). Методы наследуются, но тип this у них остаётся таким же, как и у предкового класса, за исключением случая, когда происходит переопределение виртуального метода.

Виртуальные методы

Виртуальный метод - это такой метод, который вызывается косвенно, и конкретная реализация которого может быть различной. Виртуальный метод объявленный в одном классе, может быть переопределён в классе-потомке.

Объявляется виртуальный метод с использованием ряда ключевых слов:

  • virtual - метод впервые объявляется как виртуальный. В этом случае у предкового класса не должно быть такого метода.
  • virtual pure - метод впервые объявляется как виртуальный. Реализация данного метода запрещена.
  • virtual override - метод переопределяет метод предкового класса.
  • virtual final - метод переопределяет метод предкового класса. Дальнейшей переопределение в классах-потомках для этого метода запрещено.

Виртуальные методы должны всегда иметь параметр this. Он должен быть изменяемой или неизменяемой ссылкой, byval this не допускается.

class A interface
{
    fn virtual pure Foo( this, i32 x );
}

class B polymorph
{
    fn virtual Bar( mut this, f32 y );
}

class C : A, B
{
    fn virtual override Foo( this, i32 x );
    fn virtual final Bar( mut this, f32 y );
}

fn CallFoo( A& a, i32 x )
{
    a.Foo(x);
}

fn CallBar( B &mut b, f32 y )
{
    b.Bar(y);
}

fn Test()
{
    var C mut c;
    CallFoo( c, 42 ); // Будет вызван метод С::Foo
    CallBar( c, 0.25f ); // Будет вызван метод C::Bar
    var B mut b;
    CallBar( b, 3.14f ); // Будет вызван метод B::Bar
}

Деструктор полиморфных классов всегда является виртуальным, его можно таковым и не объявлять.