Русская Википедия:Стирание типа
Стирание типа (type erasure) — идиома программирования, один из способов корректно и единообразно обрабатывать данные разного типа. А именно: переменная конкретного типа начинает храниться в ячейке памяти общего типа, а вычисление на более высоком уровне планируется так, что преобразование типов «вниз», из общего в конкретный, всегда оказывается корректным.
Некоторые источники считают, что виртуальный полиморфизм — это уже стирание типа[1].
Ключевой недостаток стирания типов — нельзя надёжно, одним просмотром памяти, узнать при выполнении, какой тип был стёрт. Преимущество стирания типов в компактности.
Примеры
Большинство машинных кодов
Как правило, команды процессора имеют очень малый набор доступных типов: целый байт (2, 4, 8, 16 байтов; какой-то из этих целых типов заодно и указатель), дробные 4 и 8 байтов.
Поверх всего этого языки высокого уровня выстраивают сложную систему типов, и компилятор сам планирует вычисление так, что если, например, загружаются данные из 4-байтовой дробной переменной, то обязательно будут вызываться машинные команды, применимые к 4-байтовому дробному.
Обобщённое программирование в Java
Шаблонное программирование в Java работает так. Когда пишут
class Wrapper<T> {
T value;
}
создаётся всего один скомпилированный класс (Wrapper.class
), и в нём переменная value
типа Object
. Но компилятор своими силами налаживает работу так, чтобы Wrapper<Integer>
не пересекался с Wrapper<String>
, и преобразования типов получаются верными.
Именно поэтому запрещается[2] шаблонно наследоваться от Throwable
, прямо или косвенно — ведь непонятно, какой из блоков вызвать в этом коде:
try {
throw new GenericException<Integer>();
}
catch (GenericException<Integer> e) {
System.err.println("Integer");
}
catch (GenericException<String> e) {
System.err.println("String");
}
Другой недопустимый код в Java, именно из-за стирания типа — вызов неизвестного заранее конструктора.
<T> T instantiateElementType(List<T> arg) {
return new T(); // Ошибка
}
В Си++ шаблонное программирование не стирает тип, а генерирует для каждой специализации свой машинный код, и аналогичная программа вполне возможна.
template <class T>
auto instantiateElementType(std::span<const T> arg) {
return std::make_unique<T>();
}
Создание виртуального полиморфизма там, где его не было…
…Или добавление второй, чисто серверной, таблицы виртуальных методов в шаблон «команда».
Предположим, что есть разные команды — например, «напечатать число» и «напечатать строку». Первая таблица виртуальных методов общая для клиента и сервера, и отвечает за «лёгкие» вещи вроде сериализации и записи в журнал. Только сервер умеет исполнять команды, и для этого нужна отдельная вторая таблица виртуальных методов.
Здесь стирание типа — это хранение команды в Шаблон:Cpp, а не в Шаблон:Cpp[3]. Таблица виртуальных методов может быть и первая — для единообразной обработки совершенно не связанных типов; так работает тип Шаблон:Cpp Си++17[3].
#include <iostream>
#include <memory>
#include <vector>
/// Общее для клиента и сервера
class ClientCommand {
public:
virtual ~ClientCommand() = default;
// функции write(), log() и прочие, нам сейчас не нужные
};
class PrintInt : public ClientCommand {
public:
int value;
PrintInt(int v) : value(v) {}
};
class PrintString : public ClientCommand {
public:
std::string value;
PrintString(std::string v) : value(std::move(v)) {}
};
/// Сервер
// Исполнение команд редко бывает шаблонным — сделаем нешаблонно…
void doExec(const PrintInt& x) { std::cout << x.value << std::endl; }
void doExec(const PrintString& x) { std::cout << x.value << std::endl; }
class ServerCommand {
public:
void exec() const { value->exec(); }
template <class Clicmd, class... Args>
static ServerCommand make(Args&&... args)
{ return ServerCommand(new Model<Clicmd>(std::forward<Args>(args)...)); }
private:
class Concept {
public:
virtual ~Concept() = default;
virtual void exec() const = 0;
};
template <class Clicmd> class Model : public Concept {
public:
static_assert(std::is_base_of_v<ClientCommand, Clicmd>);
Clicmd value;
template <class... Args> Model(Args&&... args)
: value(std::forward<Args>(args)...) {}
void exec() const override { doExec(value); }
};
std::unique_ptr<Concept> value;
ServerCommand(Concept* v) : value(v) {}
};
int main()
{
std::vector<ServerCommand> commands;
commands.push_back(ServerCommand::make<PrintInt>(42));
commands.push_back(ServerCommand::make<PrintString>("Wikipedia"));
for (auto& cmd : commands) {
cmd.exec();
}
}
Примечания