Обобщенная обертка
Часть раздела Объектно-ориентированное программирование путешествия по C на Coddy. Урок 50 из 61.
Одного void* достаточно, чтобы хранить любые data, но у него есть критический недостаток: вы теряете представление о том, какой тип он содержит. Чтобы создавать действительно полезные универсальные контейнеры, нам нужно объединить указатель на data с информацией о типе. Здесь на помощь приходит паттерн Generic Wrapper.
Идея проста: создайте struct, который объединяет void* с enum, определяющим сохранённый тип. Таким образом, оболочка всегда знает, как интерпретировать свои contents.
typedef enum {
TYPE_INT,
TYPE_STRING
} DataType;
typedef struct {
void* data;
DataType type;
} Wrapper;При хранении data вы выделяете memory для фактического значения и записываете его тип. При извлечении вы проверяете тип перед приведением:
Wrapper wrap_int(int value) {
int* p = malloc(sizeof(int));
*p = value;
return (Wrapper){ .data = p, .type = TYPE_INT };
}
void print_wrapper(Wrapper* w) {
if (w->type == TYPE_INT) {
printf("%d\n", *(int*)w->data);
} else if (w->type == TYPE_STRING) {
printf("%s\n", (char*)w->data);
}
}Этот шаблон является основой для создания типобезопасных универсальных контейнеров в C. enum действует как тег типа времени выполнения, позволяя вашему коду безопасно принимать решения о том, как обрабатывать сохранённые данные.
Задание
ЛегкоДавайте создадим типобезопасную Generic систему оболочек, которая может хранить либо integer, либо string, отслеживая, какой тип она содержит, чтобы мы могли безопасно извлечь и вывести data позже.
Вы организуете свой код в трёх файлах:
wrapper.h: Define перечислениеDataTypeсо значениямиTYPE_INTиTYPE_STRING. Затем Define структуруWrapper, которая объединяет указательvoid*на data с полем типаDataType. Declare прототипы function для создания оболочек и вывода их contents.wrapper.c: Implement функциональность оболочки:wrap_int: принимает integer, выделяет для него memory, сохраняет значение и возвращает Wrapper с подходящей меткой типаwrap_string: принимает string (char*), выделяет memory и копирует string, а затем возвращает Wrapper с меткой типа stringprint_wrapper: проверяет метку типа и соответствующим образом выводит data (integer как есть, string как есть)free_wrapper: освобождает dynamicaly выделенные data внутри оболочки
main.c: Объедините всё вместе. Считайте индикатор типа (iдля integer,sдля string), за которым следует значение. Создайте подходящую оболочку, выведите её contents и освободите memory.
Ваша программа получит два входных значения:
- Индикатор типа:
iдля integer илиsдля string - Значение, которое нужно обернуть
Пример вывода, если входными значениями являются i и 42:
42Пример вывода, если входными значениями являются s и Hello:
HelloПример вывода, если входными значениями являются i и -100:
-100Пример вывода, если входными значениями являются s и Generic Programming:
Generic ProgrammingНе забудьте использовать include guards в файле заголовка. Для копирования string выделите достаточно места для string вместе с null-терминатором, используя strlen и strcpy.
Попробуйте сами
#include <stdio.h>
#include <string.h>
#include "wrapper.h"
int main() {
char type;
scanf("%c", &type);
getchar(); // поглотить перевод строки
Wrapper w;
if (type == 'i') {
int value;
scanf("%d", &value);
// TODO: Создать целочисленную обёртку с помощью wrap_int
} else if (type == 's') {
char str[256];
fgets(str, sizeof(str), stdin);
// Удалить завершающий перевод строки, если он есть
str[strcspn(str, "\n")] = '\0';
// TODO: Создать строковую обёртку с помощью wrap_string
}
// TODO: Вывести содержимое обёртки с помощью print_wrapper
// TODO: Освободить память обёртки с помощью free_wrapper
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы модульного программирования
Заголовочные файлыСтражи включенияИсходные файлыСтатические функцииПовторение: Модульный калькулятор4Инкапсуляция
Концепция непрозрачных указателейОпределение непрозрачных структурГеттеры и сеттерыВалидация в сеттерахИтоги: Секретный ящик7Указатели на функции
Объявление указателей на функцииВызов указателей на функцииTypedef для указателей на функцииПередача функций в качестве аргументовИтоги: Диспетчеризация калькулятора10Обобщенные контейнеры
Повторение: указатели voidОбобщенная оберткаОбобщенный обмен (Swap)Обобщенное сравнениеПовторение: обобщенный массив2Объекты и методы
Структуры как объектыУказатель 'Self'Константная корректностьУказатель против значенияВспомогательные методыИтоги: Point Manager5Проект: Простой банковский счет
Настройка проектаРеализация счета3Жизненный цикл объекта
Паттерн «Конструктор»Паттерн «Деструктор»Инициализация в стекеГлубокое копированиеПовторение: String Wrapper6Наследование через композицию
Встраивание структурПравило первого элементаДоступ к элементам родителяUpcastingПовторение: Иерархия фигур9Проект: Рисование фигур
Обзор проектаРеализация кругаРеализация прямоугольникаПолиморфное использованиеКонтейнер фигурПотренируйтесь самостоятельно: Онлайн-компилятор C