Кодогенерация в Go.
Кодогенерация — автоматический процесс создания кода на основании входных данных или шаблонов.
Часто применяется в случаях, когда приходится повторять однообразные операции над множеством похожих сущностей.
Например, можно автоматически генерировать сериализаторы JSON для разных структур, шаблонные функции для конкретных типов данных и др.
Инструменты для кодогенерации в Go включают:
go generate — команда компилятора Go, позволяющая запускать скрипты для автоматической генерации кода.
Templating tools — библиотеки вроде text/template и html/template, позволяющие динамически формировать тексты или HTML-контент.
Пример простого генератора интерфейсов:
Команда go generate выполнит скрипт и сгенерирует нужный интерфейс.
Обобщенное программирование в Go реализуется через интерфейсы и дженерики.
Парадигма MapReduce полезна для параллельной обработки больших объёмов данных.
Кодогенерация позволяет упростить рутинные задачи и ускорить разработку сложных систем.
Кодогенерация — автоматический процесс создания кода на основании входных данных или шаблонов.
Часто применяется в случаях, когда приходится повторять однообразные операции над множеством похожих сущностей.
Например, можно автоматически генерировать сериализаторы JSON для разных структур, шаблонные функции для конкретных типов данных и др.
Инструменты для кодогенерации в Go включают:
go generate — команда компилятора Go, позволяющая запускать скрипты для автоматической генерации кода.
Templating tools — библиотеки вроде text/template и html/template, позволяющие динамически формировать тексты или HTML-контент.
Пример простого генератора интерфейсов:
//gen_interface.go
//+build ignore
package main
import (
"fmt"
"go/types"
"strings"
)
const template = `
type MyInterface interface {
Get%s() %s
Set%s(%s)
}`
func main() {
types := []string{"Int", "String"}
for _, t := range types {
getter := fmt.Sprintf(template, strings.Title(t), t, strings.Title(t), t)
fmt.Println(getter)
}
}
Команда go generate выполнит скрипт и сгенерирует нужный интерфейс.
Обобщенное программирование в Go реализуется через интерфейсы и дженерики.
Парадигма MapReduce полезна для параллельной обработки больших объёмов данных.
Кодогенерация позволяет упростить рутинные задачи и ускорить разработку сложных систем.
#502_GO_ODP_Q27
Какие технологические преимущества есть у языка Go?
Для каких задач Go подходит идеально?
Для каких задач Go не очень подходит, но может быть полезен?
Чем отличается goroutine от OS thread?
Как устроен сетевой ввод-вывод в Go?
Golang разработан Google с целью решения ряда практических проблем, возникающих при разработке крупных проектов.
Основные технические достоинства Go:
Высокая производительность и компактность двоичных файлов — компиляция в нативный машинный код обеспечивает высокую скорость исполнения.
Минималистичный дизайн библиотеки стандартных пакетов способствует созданию небольших исполняемых файлов.
Эффективная работа с многопоточностью — goroutines предоставляют эффективный механизм асинхронного выполнения задач, позволяя обрабатывать тысячи одновременных запросов с минимальным расходом ресурсов.
Встроенная поддержка каналов (channels) упрощает взаимодействие между горутинами и обмен данными.
Сборка мусора (GC) — автоматическая очистка неиспользуемых объектов снижает нагрузку на разработчика и улучшает устойчивость приложений.
Сборщик мусора оптимизирован для высокой пропускной способности и низкой задержки.
Статическая типизация с простым синтаксисом — типы проверяются на этапе компиляции, снижая риск ошибок во время выполнения.
Легко читаемый и лаконичный синтаксис облегчает понимание и сопровождение кода.
Приятная экосистема и стандарты качества — богатая стандартная библиотека покрывает большинство базовых потребностей (сетевые протоколы, базы данных, криптография и т.п.).
Инструментарий Go (go fmt, go vet, go test) поощряет аккуратный стиль написания кода и дисциплину тестирования.
Кросс-компиляция — возможность сборки бинарных файлов для различных платформ и архитектур (Windows, Linux, macOS, ARM и т.д.) одним нажатием кнопки.
Простота изучения и поддержки проекта — четкая философия
"Less is more" — меньше ключевых слов, минимум магии, отсутствие перегрузки операторов.
Отсутствие сложной объектно-ориентированной модели упрощает архитектуру и делает проекты легко поддерживаемыми.
Go отлично подходит для следующего набора задач:
Разработка серверных приложений и микросервисов — высокая производительность и простота развертывания делают Go идеальным выбором для backend-разработки.
Поддерживает RESTful API, RPC-сервисы, webhooks и интеграционные сервисы.
Работа с большими объемами данных и вычислительные задачи — благодаря эффективной работе с памятью и быстрым goroutines, Go прекрасно справляется с задачами обработки больших объемов данных.
Инфраструктура и DevOps-инструменты — такие утилиты, как Docker, Kubernetes, Terraform, изначально написаны на Go благодаря его эффективности и удобству кросс-компиляции.
API-серверы и шлюзы — Go способен быстро обрабатывать запросы и эффективно взаимодействовать с внешними сервисами.
Создание высоконагруженных сервисов — производительность Go на уровне C++, однако разработка быстрее и дешевле, чем на языках с более высоким порогом входа.
Хотя Go широко используется, существуют ситуации, когда его особенности становятся недостатками:
Численные вычисления (HPC) — приложения, требующие экстремальных математических расчетов (научные исследования, физика частиц, симуляции), часто выигрывают от специализированных языков (Python, Julia, Fortran).
Интерактивные графические интерфейсы (GUI) — платформы GUI вроде Qt, Electron или JavaFX поддерживают языки с богатыми фреймворками (C++, Python, JavaScript). Графические интерфейсы на чистом Go возможны, но требуют дополнительной инфраструктуры.
Параллелизм на уровне железа (GPU) — gorutines ориентированы на CPU, тогда как GPU-вычисления требуют низкоуровневых технологий (OpenCL, CUDA), поддерживаемых преимущественно на Си и C++.
Однако в перечисленных сценариях Go может играть вспомогательную роль (например, обработка сетей и логика сервера для HPC-проектов, контроль логики приложений на стороне клиента в GUI-проектах).
Какие технологические преимущества есть у языка Go?
Для каких задач Go подходит идеально?
Для каких задач Go не очень подходит, но может быть полезен?
Чем отличается goroutine от OS thread?
Как устроен сетевой ввод-вывод в Go?
Golang разработан Google с целью решения ряда практических проблем, возникающих при разработке крупных проектов.
Основные технические достоинства Go:
Высокая производительность и компактность двоичных файлов — компиляция в нативный машинный код обеспечивает высокую скорость исполнения.
Минималистичный дизайн библиотеки стандартных пакетов способствует созданию небольших исполняемых файлов.
Эффективная работа с многопоточностью — goroutines предоставляют эффективный механизм асинхронного выполнения задач, позволяя обрабатывать тысячи одновременных запросов с минимальным расходом ресурсов.
Встроенная поддержка каналов (channels) упрощает взаимодействие между горутинами и обмен данными.
Сборка мусора (GC) — автоматическая очистка неиспользуемых объектов снижает нагрузку на разработчика и улучшает устойчивость приложений.
Сборщик мусора оптимизирован для высокой пропускной способности и низкой задержки.
Статическая типизация с простым синтаксисом — типы проверяются на этапе компиляции, снижая риск ошибок во время выполнения.
Легко читаемый и лаконичный синтаксис облегчает понимание и сопровождение кода.
Приятная экосистема и стандарты качества — богатая стандартная библиотека покрывает большинство базовых потребностей (сетевые протоколы, базы данных, криптография и т.п.).
Инструментарий Go (go fmt, go vet, go test) поощряет аккуратный стиль написания кода и дисциплину тестирования.
Кросс-компиляция — возможность сборки бинарных файлов для различных платформ и архитектур (Windows, Linux, macOS, ARM и т.д.) одним нажатием кнопки.
Простота изучения и поддержки проекта — четкая философия
"Less is more" — меньше ключевых слов, минимум магии, отсутствие перегрузки операторов.
Отсутствие сложной объектно-ориентированной модели упрощает архитектуру и делает проекты легко поддерживаемыми.
Go отлично подходит для следующего набора задач:
Разработка серверных приложений и микросервисов — высокая производительность и простота развертывания делают Go идеальным выбором для backend-разработки.
Поддерживает RESTful API, RPC-сервисы, webhooks и интеграционные сервисы.
Работа с большими объемами данных и вычислительные задачи — благодаря эффективной работе с памятью и быстрым goroutines, Go прекрасно справляется с задачами обработки больших объемов данных.
Инфраструктура и DevOps-инструменты — такие утилиты, как Docker, Kubernetes, Terraform, изначально написаны на Go благодаря его эффективности и удобству кросс-компиляции.
API-серверы и шлюзы — Go способен быстро обрабатывать запросы и эффективно взаимодействовать с внешними сервисами.
Создание высоконагруженных сервисов — производительность Go на уровне C++, однако разработка быстрее и дешевле, чем на языках с более высоким порогом входа.
Хотя Go широко используется, существуют ситуации, когда его особенности становятся недостатками:
Численные вычисления (HPC) — приложения, требующие экстремальных математических расчетов (научные исследования, физика частиц, симуляции), часто выигрывают от специализированных языков (Python, Julia, Fortran).
Интерактивные графические интерфейсы (GUI) — платформы GUI вроде Qt, Electron или JavaFX поддерживают языки с богатыми фреймворками (C++, Python, JavaScript). Графические интерфейсы на чистом Go возможны, но требуют дополнительной инфраструктуры.
Параллелизм на уровне железа (GPU) — gorutines ориентированы на CPU, тогда как GPU-вычисления требуют низкоуровневых технологий (OpenCL, CUDA), поддерживаемых преимущественно на Си и C++.
Однако в перечисленных сценариях Go может играть вспомогательную роль (например, обработка сетей и логика сервера для HPC-проектов, контроль логики приложений на стороне клиента в GUI-проектах).
Отличия goroutines от OS threads.
Threads (потоки ОС) — создаются и управляются ОС.
Каждому потоку выделяется кусок оперативной памяти, и стоимость создания потока высока.
Goroutines — легкие потоки управления, создаваемые языком Go.
Их исполнение мультиплексируется поверх небольшого количества настоящих потоков ОС, что сильно экономит ресурсы и позволяет запускать десятки тысяч gorutines.
Стоимость запуска:
Создание потока ОС требует сотни килобайт памяти и занимает значительное время.
Горутины инициализируются с небольшим стеком (~2KB) и начинают выполняться мгновенно.
Управление ресурсами:
Потоки ОС используют глобальное управление потоками, и каждое ядро ОС управляет своими собственными потоками независимо.
Goroutines управляются внутренним диспетчером Go runtime, который распределяет выполнение горутин среди имеющихся ядер и потоков.
Контекст переключений:
Переключение контекста между потоками ОС дорого обходится (сохранение регистров, обновление кэшей и т.д.).
Switching context в рамках горутин осуществляется намного эффективнее, так как они сами организуют собственную очередь выполнения.
Организация сетевого ввода-вывода в Go.
Сетевое взаимодействие в Go построено вокруг концепций неблокирующего ввода-вывода и синхронного/асинхронного выполнения операций:
Неблокирующий ввод-вывод (non-blocking I/O) — операции чтения и записи сети выполняются асинхронно через механизм неблокирующих сокетов, предоставляя эффективный механизм обработки множества соединений.
Синхронизация с помощью каналов (channels) — каналы обеспечивают удобный способ обмена данными между горутинами и синхронизируют доступ к общим ресурсам.
Модель событийной петли (event loop) — внутренний диспетчер (scheduler) управляет выполнением горутин и планирует выполнение операций в очередях событий.
Шаблоны буферизации и мультиплексинга — пакеты net/http, bufio, tcp, udp предоставляют удобные способы работы с соединениями и пакетами данных.
Типичная схема сетевого взаимодействия выглядит примерно так:
Такой подход позволяет создать эффективные, высокопроизводительные серверы, способные обслуживать большое количество клиентов одновременно.
Threads (потоки ОС) — создаются и управляются ОС.
Каждому потоку выделяется кусок оперативной памяти, и стоимость создания потока высока.
Goroutines — легкие потоки управления, создаваемые языком Go.
Их исполнение мультиплексируется поверх небольшого количества настоящих потоков ОС, что сильно экономит ресурсы и позволяет запускать десятки тысяч gorutines.
Стоимость запуска:
Создание потока ОС требует сотни килобайт памяти и занимает значительное время.
Горутины инициализируются с небольшим стеком (~2KB) и начинают выполняться мгновенно.
Управление ресурсами:
Потоки ОС используют глобальное управление потоками, и каждое ядро ОС управляет своими собственными потоками независимо.
Goroutines управляются внутренним диспетчером Go runtime, который распределяет выполнение горутин среди имеющихся ядер и потоков.
Контекст переключений:
Переключение контекста между потоками ОС дорого обходится (сохранение регистров, обновление кэшей и т.д.).
Switching context в рамках горутин осуществляется намного эффективнее, так как они сами организуют собственную очередь выполнения.
Организация сетевого ввода-вывода в Go.
Сетевое взаимодействие в Go построено вокруг концепций неблокирующего ввода-вывода и синхронного/асинхронного выполнения операций:
Неблокирующий ввод-вывод (non-blocking I/O) — операции чтения и записи сети выполняются асинхронно через механизм неблокирующих сокетов, предоставляя эффективный механизм обработки множества соединений.
Синхронизация с помощью каналов (channels) — каналы обеспечивают удобный способ обмена данными между горутинами и синхронизируют доступ к общим ресурсам.
Модель событийной петли (event loop) — внутренний диспетчер (scheduler) управляет выполнением горутин и планирует выполнение операций в очередях событий.
Шаблоны буферизации и мультиплексинга — пакеты net/http, bufio, tcp, udp предоставляют удобные способы работы с соединениями и пакетами данных.
Типичная схема сетевого взаимодействия выглядит примерно так:
listener, _ := net.Listen("tcp", ":8080") // слушаем входящие соединения
for {
conn, _ := listener.Accept() // принимаем новое соединение
go handleConnection(conn) // отправляем в отдельную горутину
}
func handleConnection(conn net.Conn) {
buf := bufio.NewReader(conn) // создаем буферированный reader
for {
line, err := buf.ReadBytes('\n') // читаем строку из соединения
if err != nil {
break
}
processData(line) // обрабатываем полученные данные
}
}Такой подход позволяет создать эффективные, высокопроизводительные серверы, способные обслуживать большое количество клиентов одновременно.
#503_GO_ODP_Q28
Какие технологические недостатки есть у языка Go?
Для каких задач были бы полезны дженерики?
Как превратить []io.ReadWriter в []io.Reader?
Несмотря на ряд значительных преимуществ, язык Go имеет ограничения и слабые стороны, которые важно учитывать при выборе технологии для конкретного проекта:
Ограниченность функционала дженериков — до Go 1.18 (включительно) поддержка дженериков отсутствовала вовсе, что приводило к дублированию кода при обработке коллекций разнотипных данных.
После введения дженериков в Go 1.18 многие задачи стали решаться удобнее, но функционал дженериков ограничен по сравнению с аналогичными средствами в других языках (Java, C#), что иногда вызывает неудобства.
Отсутствие перегрузки методов и операторов — разработчик вынужден использовать разные имена для методов с одинаковой сигнатурой, что увеличивает объем кода и ухудшает читаемость.
Нет возможности переопределять операторы (+, -, *, /), что затрудняет реализацию высокооптимизированных математических моделей.
Недостаточность рефлексии (reflection) — отражение в Go ограничено возможностями статической проверки типов, что препятствует глубокому использованию мета-данных и динамических возможностей, присутствующих в других языках (например, Python, Ruby).
Система памяти (memory model) — концептуальная простота работы с памятью скрывает детали оптимизации, такие как pinning и garbage collector, что может привести к неоправданным потерям производительности в критичных приложениях.
Фиксированная структура пакета (vendor directory) — в ранних версиях Go возникали трудности с управлением зависимостями, хотя ситуация улучшилась с появлением модуля управления зависимостями (Go modules).
Неполноценная поддержка макросов и AOP (Aspect-Oriented Programming) — макросы отсутствуют в Go, что ограничивает применение техники аспектно-ориентированного программирования (AOP), удобной для внедрения сквозных механизмов логирования, безопасности и транзакционности.
Эти аспекты показывают, что Go не является панацеей и не решает абсолютно все задачи одинаково эффективно.
Где полезны дженерики (generics)?
Дженерики полезны в тех случаях, когда нужно написать общий алгоритм или структуру данных, работающую с различными типами данных:
Коллекции и контейнеры:
Дженерики облегчают написание списков, карт, деревьев и прочих структур данных, работающих с произвольными типами.
Алгоритмы обработки данных:
Алгоритмы сортировки, фильтрации, отображения данных (map/reduce) работают с несколькими типами данных, позволяя повторно использовать тот же самый код.
Интерфейсы и классы с общими операциями:
Можно создать обобщённые интерфейсы и абстрактные классы, которые используются разными типами данных.
До Go 1.18 приходилось либо копипастить код, либо применять рефлексию и преобразование типов через interface{}, что увеличивало накладные расходы и уменьшало безопасность.
Теперь дженерики решают проблему повторения однотипного кода, обеспечивая большую гибкость и улучшая качество кода.
Превращение []io.ReadWriter в []io.Reader
Часто возникает необходимость конвертации срезов интерфейсов одного типа в другой. Рассмотрим конкретный пример преобразования среза интерфейсов ReadWriter в срез интерфейсов Reader:
Объект io.ReadWriter реализует оба интерфейса: Reader и Writer.
Поскольку Reader является частью ReadWriter, его можно безопасно кастовать обратно к типу Reader, не теряя функциональность.
Такая техника конверсии полезна, когда нужно применить общие операции чтения данных к объектам, обладающим дополнительными возможностями (запись данных).
Какие технологические недостатки есть у языка Go?
Для каких задач были бы полезны дженерики?
Как превратить []io.ReadWriter в []io.Reader?
Несмотря на ряд значительных преимуществ, язык Go имеет ограничения и слабые стороны, которые важно учитывать при выборе технологии для конкретного проекта:
Ограниченность функционала дженериков — до Go 1.18 (включительно) поддержка дженериков отсутствовала вовсе, что приводило к дублированию кода при обработке коллекций разнотипных данных.
После введения дженериков в Go 1.18 многие задачи стали решаться удобнее, но функционал дженериков ограничен по сравнению с аналогичными средствами в других языках (Java, C#), что иногда вызывает неудобства.
Отсутствие перегрузки методов и операторов — разработчик вынужден использовать разные имена для методов с одинаковой сигнатурой, что увеличивает объем кода и ухудшает читаемость.
Нет возможности переопределять операторы (+, -, *, /), что затрудняет реализацию высокооптимизированных математических моделей.
Недостаточность рефлексии (reflection) — отражение в Go ограничено возможностями статической проверки типов, что препятствует глубокому использованию мета-данных и динамических возможностей, присутствующих в других языках (например, Python, Ruby).
Система памяти (memory model) — концептуальная простота работы с памятью скрывает детали оптимизации, такие как pinning и garbage collector, что может привести к неоправданным потерям производительности в критичных приложениях.
Фиксированная структура пакета (vendor directory) — в ранних версиях Go возникали трудности с управлением зависимостями, хотя ситуация улучшилась с появлением модуля управления зависимостями (Go modules).
Неполноценная поддержка макросов и AOP (Aspect-Oriented Programming) — макросы отсутствуют в Go, что ограничивает применение техники аспектно-ориентированного программирования (AOP), удобной для внедрения сквозных механизмов логирования, безопасности и транзакционности.
Эти аспекты показывают, что Go не является панацеей и не решает абсолютно все задачи одинаково эффективно.
Где полезны дженерики (generics)?
Дженерики полезны в тех случаях, когда нужно написать общий алгоритм или структуру данных, работающую с различными типами данных:
Коллекции и контейнеры:
Дженерики облегчают написание списков, карт, деревьев и прочих структур данных, работающих с произвольными типами.
Алгоритмы обработки данных:
Алгоритмы сортировки, фильтрации, отображения данных (map/reduce) работают с несколькими типами данных, позволяя повторно использовать тот же самый код.
Интерфейсы и классы с общими операциями:
Можно создать обобщённые интерфейсы и абстрактные классы, которые используются разными типами данных.
До Go 1.18 приходилось либо копипастить код, либо применять рефлексию и преобразование типов через interface{}, что увеличивало накладные расходы и уменьшало безопасность.
Теперь дженерики решают проблему повторения однотипного кода, обеспечивая большую гибкость и улучшая качество кода.
Превращение []io.ReadWriter в []io.Reader
Часто возникает необходимость конвертации срезов интерфейсов одного типа в другой. Рассмотрим конкретный пример преобразования среза интерфейсов ReadWriter в срез интерфейсов Reader:
package main
import (
"io"
"fmt"
)
func convertToReaders(rw []io.ReadWriter) []io.Reader {
readers := make([]io.Reader, len(rw)) // Создаем новый срез нужного размера
for i, rwItem := range rw {
readers[i] = io.Reader(rwItem) // Присваиваем интерфейс Reader каждому элементу
}
return readers
}
func main() {
rwSlice := []io.ReadWriter{ /* заполните необходимыми объектами */ }
readerSlice := convertToReaders(rwSlice)
fmt.Println(readerSlice)
}
Объект io.ReadWriter реализует оба интерфейса: Reader и Writer.
Поскольку Reader является частью ReadWriter, его можно безопасно кастовать обратно к типу Reader, не теряя функциональность.
Такая техника конверсии полезна, когда нужно применить общие операции чтения данных к объектам, обладающим дополнительными возможностями (запись данных).
#504_Cpp_IdPt
Рекомендации С.Мэйерса из книги (издательство ДМК 2006 г):
"Эффективное использование С++. 55 верных советов улучшить структуру и код ваших программ".
1. Относитесь к С++ как к конгломерату языков.
Чтобы увидеть смысл в С++, нужно распознавать его основные подъязыки:
— С. В глубине своей С++ все еще основан на С.
— Объектно-ориентированный С++.
— С++ с шаблонами.
— STL.
CЛЕДУЕТ ПОМНИТЬ:
2. Предпочитайте const, enum и inline использованию #define.
CЛЕДУЕТ ПОМНИТЬ:
3. Везде, где только можно, используйте const.
CЛЕДУЕТ ПОМНИТЬ:
4. Прежде чем использовать объекты, убедитесь, что они инициализированы.
CЛЕДУЕТ ПОМНИТЬ:
5. Какие функции С++ создает и вызывает молча?
CЛЕДУЕТ ПОМНИТЬ:
6. Явно запрещайте компилятору генерировать функции, которые вам не нужны.
CЛЕДУЕТ ПОМНИТЬ:
7. Объявляйте деструкторы виртуальными в полиморфном базовом классе.
CЛЕДУЕТ ПОМНИТЬ:
8. Не позволяйте исключениям покидать деструкторы.
CЛЕДУЕТ ПОМНИТЬ:
9. Никогда не вызывайте виртуальные функции в конструкторе
или деструкторе.
CЛЕДУЕТ ПОМНИТЬ:
10. Операторы присваивания должны возвращать ссылку на *this.
CЛЕДУЕТ ПОМНИТЬ:
.
Рекомендации С.Мэйерса из книги (издательство ДМК 2006 г):
"Эффективное использование С++. 55 верных советов улучшить структуру и код ваших программ".
1. Относитесь к С++ как к конгломерату языков.
Чтобы увидеть смысл в С++, нужно распознавать его основные подъязыки:
— С. В глубине своей С++ все еще основан на С.
— Объектно-ориентированный С++.
— С++ с шаблонами.
— STL.
CЛЕДУЕТ ПОМНИТЬ:
— Правила эффективного программирования меняются в зависимости от части С++, которую вы используете.
2. Предпочитайте const, enum и inline использованию #define.
CЛЕДУЕТ ПОМНИТЬ:
— Директиве #define следует предпочесть константные объекты и перечисления (enum).
— Вместо имитирующих функции макросов, определенных через #define, лучше применять встроенные функции (inline).
3. Везде, где только можно, используйте const.
CЛЕДУЕТ ПОМНИТЬ:
— Объявление чего-либо с модификатором const помогает компиляторам обнаруживать ошибки.
const можно использовать с объектами в любой области действия, с параметрами функций и возвращаемых значений, а также с функциями-членами в целом.
— Компиляторы проверяют побитовую константность, но вы должны программировать, применяя логическую константность.
— Когда константные и неконстантные функции-члены имеют, по сути, одинаковую реализацию, то дублирования кода можно избежать, заставив неконстантную версию вызывать константную.
4. Прежде чем использовать объекты, убедитесь, что они инициализированы.
CЛЕДУЕТ ПОМНИТЬ:
— Всегда вручную инициализируйте объекты встроенных типов, по
скольку С++ делает это не всегда.
— В конструкторе отдавать предпочтение применению списков инициализации членов перед прямым присваиванием значений в теле конструктора.
Перечисляйте данные-члены в списке инициализации в том же порядке, в каком они объявлены в классе.
— Избегайте проблем с порядком инициализации в разных единицах трансляции, заменяя нелокальные статические объекты локальными статическими объектами.
5. Какие функции С++ создает и вызывает молча?
CЛЕДУЕТ ПОМНИТЬ:
— Компилятор может неявно генерировать для класса конструктор по умолчанию, конструктор копирования, оператор присваивания и деструктор.
6. Явно запрещайте компилятору генерировать функции, которые вам не нужны.
CЛЕДУЕТ ПОМНИТЬ:
— Чтобы отключить функциональность, автоматически предоставляемую компилятором, объявите соответствующую функцию-член закрытой и не включайте ее реализацию.
Наследование базовому классу типа UncopyaЬle - один из способов сделать это.
— Начиная с версии С++11 конструктор копирования и оператор присваивания можно исключить с помощью слова delete
7. Объявляйте деструкторы виртуальными в полиморфном базовом классе.
CЛЕДУЕТ ПОМНИТЬ:
— Полиморфные базовые классы должны объявлять виртуальные деструкторы.
Если класс имеет хотя бы одну виртуальную функцию, он должен иметь виртуальный деструктор.
— В классах, не предназначенных для использования в качестве базовых или для полиморфного применения, не следует объявлять виртуальные деструкторы.
8. Не позволяйте исключениям покидать деструкторы.
CЛЕДУЕТ ПОМНИТЬ:
— Деструкторы никогда не должны возбуждать исключений.
Если функция, вызываемая в деструкторе, может это сделать, то деструктор обязан перехватывать все исключения, а затем "проглатывать" их либо прерывать программу.
— Если клиенты класса нуждаются в возможности реагировать на исключения во время некоторой операции, то класс должен предоставить обычную функцию (то есть не деструктор), которая эту операцию выполнит.
9. Никогда не вызывайте виртуальные функции в конструкторе
или деструкторе.
CЛЕДУЕТ ПОМНИТЬ:
— Не вызывайте виртуальные функции во время работы конструкторов и деструкторов, потому что такие вызовы никогда не дойдут до производных классов, расположенных в иерархии наследования ниже того, который сейчас конструируется или уничтожается.
10. Операторы присваивания должны возвращать ссылку на *this.
CЛЕДУЕТ ПОМНИТЬ:
— Пишите операторы присваивания так, чтобы они возвращали ссылку на *this.
.
11. В operator= осуществляйте проверку на присваивание самому себе.
CЛЕДУЕТ ПОМНИТЬ:
12. Копируйте все части объекта.
CЛЕДУЕТ ПОМНИТЬ:
13. Используйте объекты для управления ресурсами.
CЛЕДУЕТ ПОМНИТЬ:
14. Тщательно продумывайте поведение при копировании классов,
управляющих ресурсами.
CЛЕДУЕТ ПОМНИТЬ:
15. Предоставляйте доступ к самим ресурсам из управляющих
ими классов.
CЛЕДУЕТ ПОМНИТЬ:
16. Используйте одинаковые формы new и delete.
CЛЕДУЕТ ПОМНИТЬ:
17. Помещение в "интеллектуальный" указатель объекта, выделенного с помощью new, лучше располагать в отдельном предложении.
CЛЕДУЕТ ПОМНИТЬ:
18. Проектируйте интерфейсы так, чтобы их легко было использовать правильно и трудно - неправильно.
CЛЕДУЕТ ПОМНИТЬ:
.
CЛЕДУЕТ ПОМНИТЬ:
— Убедитесь, что operator= правильно ведет себя, когда объект присваивается самому себе. Для этого можно сравнить адреса исходного и целевого объектов, аккуратно упорядочить предложения или применить идиому копирования обменом.
— Убедитесь, что все функции, оперирующие более чем одним объектом, ведут себя корректно при совпадении двух или более объектов.
12. Копируйте все части объекта.
CЛЕДУЕТ ПОМНИТЬ:
— Копирующие функции должны гарантировать копирование всех членов-данных объекта и частей его базовых классов.
— Не пытайтесь реализовать одну из копирующих функций в терминах другой. Вместо этого поместите общую функциональность в третью функцию, которую вызовут обе.
13. Используйте объекты для управления ресурсами.
CЛЕДУЕТ ПОМНИТЬ:
— Чтобы предотвратить утечку ресурсов, используйте объекты RAII, которые захватывают ресурсы в своих конструкторах и освобождают в деструкторах.
— Два часто используемых класса RAII - это tr1::shared_ptr и auto_ptr.
Обычно лучше остановить выбор на классе tr1::shared_ptr, потому что его поведение при копировании соответствует интуитивным ожиданиям.
Что касается auto_ptr, то после копирования он уже не указывает ни на какой объект.
14. Тщательно продумывайте поведение при копировании классов,
управляющих ресурсами.
CЛЕДУЕТ ПОМНИТЬ:
— Копирование RАII-объектов влечет за собой копирование ресурсов, которыми они управляют, поэтому поведение ресурса при копировании определяет поведение RАII-объекта.
— Обычно при реализации RАII-классов применяется одна из двух схем: запрет копирования или подсчет ссылок, но возможны и другие варианты.
15. Предоставляйте доступ к самим ресурсам из управляющих
ими классов.
CЛЕДУЕТ ПОМНИТЬ:
— Программные интерфейсы (API) часто требуют прямого обращения к ресурсам. Именно поэтому каждый RAII-клacc должен предоставлять возможность получения доступа к ресурсу, которым он управляет.
— Доступ может быть обеспечен посредством явного либо неявного преобразования. Вообще говоря, явное преобразование безопаснее, но не явное более удобно для пользователей.
16. Используйте одинаковые формы new и delete.
CЛЕДУЕТ ПОМНИТЬ:
— Если вы используете [] в выражении new, то должны применять [] и в соответствующем выражении delete. Если вы не используете квадратные скобки [] в выражении new, то не должны использовать их и в соответствующем выражении delete.
17. Помещение в "интеллектуальный" указатель объекта, выделенного с помощью new, лучше располагать в отдельном предложении.
CЛЕДУЕТ ПОМНИТЬ:
— Помещайте объекты, выделенные оператором new, в "интеллектуальные" указатели в отдельном предложении.
В противном случае такие вызовы могут привести к утечкам ресурсов, если возникнет исключение.
18. Проектируйте интерфейсы так, чтобы их легко было использовать правильно и трудно - неправильно.
CЛЕДУЕТ ПОМНИТЬ:
— Хорошие интерфейсы легко использовать правильно и трудно использовать неправильно.
Нужно стремиться обеспечить эти характеристики в интерфейсах.
— Для обеспечения корректного использования интерфейсы должны быть согласованы и совместимы со встроенными типами.
— Для предотвращения ошибок применяют следующие способы:
- создание новых типов;
- ограничение допустимых операций над этими типами;
- ограничение допустимых значений;
- освобождение пользователя от обязанностей по управлению ресурсами.
— Класс tr1::shared_ptr поддерживает пользовательские функции-чистильщики. Это снимает "проблему нескольких DLL" и может быть использовано для автоматического освобождения мьютекса (см. правило 14).
.
19. Рассматривайте проектирование класса как проектирование типа.
Проектирование почти любого класса ставит перед разработчиком вопросы, ответы на которые часто ограничивают спектр возможных решений:
— Как должны создаваться и уничтожаться объекты нового типа?
От ответа на этот вопрос зависит дизайн конструкторов и деструкторов, а равно функций распределения и освобождения памяти (оператор new, оператор пеw[], оператор delete и оператор delete[]), если вы собираетесь их переопределить.
— Чем должна отличаться инициализация объекта от присваивания значе ний?
Ответ на этот вопрос определяет разницу в поведении между конструкторами и операторами присваивания.
Важно не путать инициализацию с присваиванием, потому что им соответствуют разные вызовы функций (см. правило 4).
— Что означает для объектов нового типа быть переданными по значению?
Помните, что конструктор копирования определяет реализацию передачи по значению для данного типа.
— Каковы ограничения на допустимые значения вашего нового типа?
Обычно только некоторые комбинации значений данных-членов класса являются правильными.
Эти комбинации определяют инварианты, которые должен поддерживать класс.
А инварианты уже диктуют, как следует контролировать ошибки в функциях-членах, в особенности в конструкторах, операторах присваивания и функциях установки значений ("setter" functions).
Могут быть также затронуты исключения, которые возбуждают ваши функции, и спецификации этих исключений.
— Укладьmается ли ваш новыii тип в граф наследования?
Наследуя классы от других, нужно следовать ограничениям, налагаемым базовыми классами.
В частности, нужно учитывать, как объявлены в них функции-члены: виртуальньши или нет (см. правила 34 и 36).
Если необходимо, чтобы класс могли наследовать другие, то нужно тщательно продумать, какие функции объявить виртуальны.ми; в особенности это относится к деструктору (правило 7).
— Какие варианты преобразования типов допустимы для нового типа?
Новый тип существует в море других типов, поэтому должны ли быть предусмотрены варианты преобразования между проектируемым типом и другими?
Если необходимо разрешить неявное преобразование объекта типа Т1 в объект типа Т2, придется либо написать функцию преобразования в классе Т1 (то есть орегаtог Т2), либо неявный конструктор в классе Т2, который может быть вызван с единственным аргументом.
Если же необходимо разрешить только явные преобразования, то нужно будет написать специальные функции, но ни в коем случае не делать их операторами преобразования или не-explicit конструкторами с одним аргументом.
— Какие операторы и функции имеют смысл для нового тнпа?
Ответ на этот вопрос определяет набор функций, которые необходимо объявить в новом классе. Некоторые из них будут функциями-членами, другие - нет (см. правила 23, 24 и 46).
— Какие стандартные функции должны стать недоступными?
Их нужно будет объявить, закрытыми (правило 6).
— Кто должен получить доступ к членам вашего нового типа?
Ответ на этот вопрос помогает определить, какие члены должны быть открытыми (public), какие — защищенными (protected) и какие — закрытыми (private).
Также преlстоит решить, какие классы и/или функции должны быть
друзьями класса, а также когда имеет смысл вложить один класс внутрь другого.
— Что такое "необъявленный иmерфейс" нового типа?
Какого рода гарантии мoryт быть предоставлены относительно производительности, безопасности относительно исключений (см. правило 29) и использования ресурсов (например, блокировок и динамической памяти)?
Такого рода гарантии определяют ограничения на реализацию нового класса.
— Насколько общий новый тип?
Возможно, в действительности не нужно определять новый тип.
Возможно, необходимо определить целое семейство типов. Если это так, то нужно определять не новый класс, а новый шаблон классa.
Проектирование почти любого класса ставит перед разработчиком вопросы, ответы на которые часто ограничивают спектр возможных решений:
— Как должны создаваться и уничтожаться объекты нового типа?
От ответа на этот вопрос зависит дизайн конструкторов и деструкторов, а равно функций распределения и освобождения памяти (оператор new, оператор пеw[], оператор delete и оператор delete[]), если вы собираетесь их переопределить.
— Чем должна отличаться инициализация объекта от присваивания значе ний?
Ответ на этот вопрос определяет разницу в поведении между конструкторами и операторами присваивания.
Важно не путать инициализацию с присваиванием, потому что им соответствуют разные вызовы функций (см. правило 4).
— Что означает для объектов нового типа быть переданными по значению?
Помните, что конструктор копирования определяет реализацию передачи по значению для данного типа.
— Каковы ограничения на допустимые значения вашего нового типа?
Обычно только некоторые комбинации значений данных-членов класса являются правильными.
Эти комбинации определяют инварианты, которые должен поддерживать класс.
А инварианты уже диктуют, как следует контролировать ошибки в функциях-членах, в особенности в конструкторах, операторах присваивания и функциях установки значений ("setter" functions).
Могут быть также затронуты исключения, которые возбуждают ваши функции, и спецификации этих исключений.
— Укладьmается ли ваш новыii тип в граф наследования?
Наследуя классы от других, нужно следовать ограничениям, налагаемым базовыми классами.
В частности, нужно учитывать, как объявлены в них функции-члены: виртуальньши или нет (см. правила 34 и 36).
Если необходимо, чтобы класс могли наследовать другие, то нужно тщательно продумать, какие функции объявить виртуальны.ми; в особенности это относится к деструктору (правило 7).
— Какие варианты преобразования типов допустимы для нового типа?
Новый тип существует в море других типов, поэтому должны ли быть предусмотрены варианты преобразования между проектируемым типом и другими?
Если необходимо разрешить неявное преобразование объекта типа Т1 в объект типа Т2, придется либо написать функцию преобразования в классе Т1 (то есть орегаtог Т2), либо неявный конструктор в классе Т2, который может быть вызван с единственным аргументом.
Если же необходимо разрешить только явные преобразования, то нужно будет написать специальные функции, но ни в коем случае не делать их операторами преобразования или не-explicit конструкторами с одним аргументом.
— Какие операторы и функции имеют смысл для нового тнпа?
Ответ на этот вопрос определяет набор функций, которые необходимо объявить в новом классе. Некоторые из них будут функциями-членами, другие - нет (см. правила 23, 24 и 46).
— Какие стандартные функции должны стать недоступными?
Их нужно будет объявить, закрытыми (правило 6).
— Кто должен получить доступ к членам вашего нового типа?
Ответ на этот вопрос помогает определить, какие члены должны быть открытыми (public), какие — защищенными (protected) и какие — закрытыми (private).
Также преlстоит решить, какие классы и/или функции должны быть
друзьями класса, а также когда имеет смысл вложить один класс внутрь другого.
— Что такое "необъявленный иmерфейс" нового типа?
Какого рода гарантии мoryт быть предоставлены относительно производительности, безопасности относительно исключений (см. правило 29) и использования ресурсов (например, блокировок и динамической памяти)?
Такого рода гарантии определяют ограничения на реализацию нового класса.
— Насколько общий новый тип?
Возможно, в действительности не нужно определять новый тип.
Возможно, необходимо определить целое семейство типов. Если это так, то нужно определять не новый класс, а новый шаблон классa.
— Действительно ли новый тип представляет собой то, что нужно?
Если определение нового производного класса только расширяет функциональность существующего класса, возможно, этой цели
лучше достичь простым определением одной или более функций-нечленов либо шаблонов.
CЛЕДУЕТ ПОМНИТЬ:
20. Предпочитайте передачу по ссылке на const передаче по значению.
CЛЕДУЕТ ПОМНИТЬ:
21. Не пытайтесь вернуть ссылку, когда должны вернуть объект.
CЛЕДУЕТ ПОМНИТЬ:
22. Объявляйте данные-члены закрытыми.
CЛЕДУЕТ ПОМНИТЬ:
23. Предпочитайте функциям-членам функции, не являющиеся ни членами, ни друзьями класса.
CЛЕДУЕТ ПОМНИТЬ:
24. Объявляйте функции, не являющиеся членами, когда преобразование типов должно быть применимо ко всем параметрам.
CЛЕДУЕТ ПОМНИТЬ:
25. Подумайте о поддержке функции swap, не возбуждающей исключений.
CЛЕДУЕТ ПОМНИТЬ:
26. Откладывайте определение переменных насколько возможно.
CЛЕДУЕТ ПОМНИТЬ:
27. Не зпоупотребпяйте приведением типов.
CЛЕДУЕТ ПОМНИТЬ:
28. Избегайте возвращения "дескрипторов" внуrренних данных.
CЛЕДУЕТ ПОМНИТЬ:
.
Если определение нового производного класса только расширяет функциональность существующего класса, возможно, этой цели
лучше достичь простым определением одной или более функций-нечленов либо шаблонов.
CЛЕДУЕТ ПОМНИТЬ:
— Проектирование класса - это проектирование типа.
Прежде чем определять новый тип, убедитесь, что рассмотрены все вопросы, которые обсуждаются в настоящем правиле.
20. Предпочитайте передачу по ссылке на const передаче по значению.
CЛЕДУЕТ ПОМНИТЬ:
— Передаче по значению предпочитайте передачу по ссылке на константу.
Обычно это более эффективно и позволяет избежать проблемы "срезки".
— Это правило не касается встроенных типов, итераторов и функциональных объектов STL.
Для них передача по значению обычно подходит больше.
21. Не пытайтесь вернуть ссылку, когда должны вернуть объект.
CЛЕДУЕТ ПОМНИТЬ:
— Никогда не возвращайте указатель или ссылку на локальный объект, ссылку на объект, распределенный в "куче", либо указатель или ссылку на локальный статический объект, если есть шанс, что понадобится более, чем один экземпляр такого объекта. (в правиле 4 приведен пример ситуации, когда возврат ссылки на локальный статический объект имеет смысл, по крайней мере, в однопоточных средах).
22. Объявляйте данные-члены закрытыми.
CЛЕДУЕТ ПОМНИТЬ:
— Объявляйте данные-члены закрытыми (private).
Это дает клиентам синтаксически однородный доступ к данным, обеспечивает возможность тонкого управления доступом, позволяет гарантировать инвариантность и предоставляет авторам рсализации классов гибкость.
— Защищенные члены не более инкапсулированы, чем открытые.
23. Предпочитайте функциям-членам функции, не являющиеся ни членами, ни друзьями класса.
CЛЕДУЕТ ПОМНИТЬ:
— Предпочитайте функциям-членам функции, не яnляющиеся ни членами, ни друзьями класса.
Это повышает степень инкапсулянии н расширяемости, а также гибкость "упаковки" функциональности.
24. Объявляйте функции, не являющиеся членами, когда преобразование типов должно быть применимо ко всем параметрам.
CЛЕДУЕТ ПОМНИТЬ:
— Если преобразование типов должно быть применимо ко всем параметрам функции (включая и скрытый параметр this), то функция не должна быть членом класса.
25. Подумайте о поддержке функции swap, не возбуждающей исключений.
CЛЕДУЕТ ПОМНИТЬ:
— Предоставьте функцию-член swap, если std::swap работает с новым типом неэффективно.
Убедитесь, что она не возбуждает исключений.
— Если вы предоставляете функцию-член swap, то также предоставьте свободную функцию, вызывающую функцию-член. Для классов (не шаблонов) специализируйте также std::swap.
— Когда вызывается swap, используйте using-объявление, вводящее std::swap в область видимости, и вызывайте swap без квалификатора пространства имен.
— Допускается предоставление полной специализации шаблонов, находящихся в пространстве имен std, для пользовательских типов, но никогда не нытайтссь добавить в пространство std что-либо новое.
26. Откладывайте определение переменных насколько возможно.
CЛЕДУЕТ ПОМНИТЬ:
— Откладывайте определение переменных насколько возможно. Это делает программы яснее и повышает их эффективность.
27. Не зпоупотребпяйте приведением типов.
CЛЕДУЕТ ПОМНИТЬ:
— Избегайте насколько возможно приведений типов, особенно dynamic_cast, в критичном по производительности коде.
Если дизайн требует приведения, попытайтесь разработать альтернативу, где такой необходимости не возникает.
— Когда приведение типа необходимо, постарайтесь скрыть его внутри функции. Тогда пользователи смогут вызывать эту функцию вместо помещения приведения в их собственный код.
— Предпочитайте приведения в стиле С++ старому стилю. Их легче увидеть, и они более избирательны.
28. Избегайте возвращения "дескрипторов" внуrренних данных.
CЛЕДУЕТ ПОМНИТЬ:
— Избегайте возвращать «дескрипторы» (ссылки, указатели, итераторы) внутренних данных объекта.
Это повышает степень инкапсуляции, помогает константным функциям-членам быть константными и минимизирует вероятность появления «висячих дескрипторов».
.
29. Стремитесь, чтобы программа была безопасна относительно исключений.
CЛЕДУЕТ ПОМНИТЬ:
30. Тщательно обдумывайте использование встроенных функций.
CЛЕДУЕТ ПОМНИТЬ:
31. Уменьшайте зависимости файлов при компиляции.
CЛЕДУЕТ ПОМНИТЬ:
32. Используйте открытое наследование для моделирования отношения «является».
CЛЕДУЕТ ПОМНИТЬ:
33. Не скрывайте унаследованные имена.
CЛЕДУЕТ ПОМНИТЬ:
34. Различайте наследование интерфейса и наследование реализации.
CЛЕДУЕТ ПОМНИТЬ:
35. Рассмотрите альтернативы виртуальным функциям.
CЛЕДУЕТ ПОМНИТЬ:
36. Никогда не переопределяйте наследуемые невирrуальные функции.
CЛЕДУЕТ ПОМНИТЬ:
37. Никогда не переопределяйте наследуемое значение аргумента функции по умолчанию.
CЛЕДУЕТ ПОМНИТЬ:
38. Моделируйте отношение «содержит» или «реализуется посредством» с помощью композиции.
CЛЕДУЕТ ПОМНИТЬ:
CЛЕДУЕТ ПОМНИТЬ:
— Безопасные относительно исключений функции не допускают утечки ресурсов и повреждения структур данных, даже в случае возбуждения исключений.
Такие функции предоставляют базовую гарантию, строгую гарантию либо гарантию полного отсутствия исключений.
— Строгая гарантия часто может быть реализована посредством копирования и обмена, но предоставлять ее для всех функций непрактично.
— Функция обычно может предоставить гарантию не строже, чем самая слабая гарантия, обеспечиваемая вызываемыми из нее функциями.
30. Тщательно обдумывайте использование встроенных функций.
CЛЕДУЕТ ПОМНИТЬ:
— Делайте встраиваемыми только небольшие, часто вызываемые функции.
Это облегчит отладку, даст возможность выполнять обновления библиотек на двоичном уровне, уменьшит эффект "разбухания" кода и поможет повысить быстродействие программы.
— Не объявляйте шаблоны функций встроенными только потому, что они появляются в заголовочных файлах.
31. Уменьшайте зависимости файлов при компиляции.
CЛЕДУЕТ ПОМНИТЬ:
— Основная идея уменьшения зависимостей на этапе компиляции состоит в том, чтобы заменить зависимость от определения зависимостью от объявления.
Эта идея лежит в основе двух подходов: классов-дескрипторов и интерфейсных классов.
— Заголовочные файлы библиотек должны существовать в обеих формах: полной и содержащей только объявления.
Это справедливо независимо от того, включают они шаблоны или нет.
32. Используйте открытое наследование для моделирования отношения «является».
CЛЕДУЕТ ПОМНИТЬ:
— Открытое наследование означает «является». Все, что применимо к базовому классу, должно быть применимо также и к производным от него классам, потому что каждый объект производного класса является также объектом базового класса.
33. Не скрывайте унаследованные имена.
CЛЕДУЕТ ПОМНИТЬ:
— Имена в производных классах скрывают имена из базовых классов.
При открытом наследовании это всегда нежелательно.
— Чтобы сделать скрытые имена видимыми, используйте using-объявления либо перенаправляющие функции.
34. Различайте наследование интерфейса и наследование реализации.
CЛЕДУЕТ ПОМНИТЬ:
— Наследование интерфейса отличается от наследования реализации.
При открытом наследовании производные классы всегда наследуют интерфейсы базовых классов.
— Чисто виртуальные функции означают, что наследуется только интерфейс.
— Обычные виртуальные функции означают, что наследуются интерфейс и реализация по умолчанию.
— Невиртуальные функции означают, что наследуются интерфейс и обязательная реализация.
35. Рассмотрите альтернативы виртуальным функциям.
CЛЕДУЕТ ПОМНИТЬ:
— К числу альтернатив виртуальным функциям относятся идиома NVI и различные формы паттерна проектирования "Стратегия".
Идиома NVI сама по себе - это пример реализации паттерна "Шаблонный Метод".
— Недостаток переноса функциональности из функций-членов вовне класса заключается в том, что функциям-нечленам недостает прав доступа к закрытым членам класса.
— Объекты tr1::function работают как обобщенные указатели на функции.
Такие объекты поддерживают все вызываемые сущности, совместимые с сигнатурой целевой функции.
36. Никогда не переопределяйте наследуемые невирrуальные функции.
CЛЕДУЕТ ПОМНИТЬ:
— Никогда не переопределяйте наследуемые невиртуальные функции.
37. Никогда не переопределяйте наследуемое значение аргумента функции по умолчанию.
CЛЕДУЕТ ПОМНИТЬ:
— Никогда не переопределяйте наследуемые значения аргументов по умолчанию, потому что аргументы по умолчанию связываются статически, тогда как виртуальные функции (а только их и можно переопределять) динамически.
38. Моделируйте отношение «содержит» или «реализуется посредством» с помощью композиции.
CЛЕДУЕТ ПОМНИТЬ:
— Семантика композиции кардинально отличается от семантики открытого наследования.
— В предметной области композиция означает "содержит".
В области реализации она означает "реализовано посредством".
39. Продумывайте подход к использованию закрытого наследования.
CЛЕДУЕТ ПОМНИТЬ:
40. Продумывайте подход к использованию множественного наследования.
CЛЕДУЕТ ПОМНИТЬ:
41. Разберитесь в том, что такое неявные интерфейсы и полиморфизм на этапе компиляции.
CЛЕДУЕТ ПОМНИТЬ:
42. Усвойте оба значения ключевого слова typename.
CЛЕДУЕТ ПОМНИТЬ:
43. Необходимо знать, как обращаться к именам в шаблонных базовых классах.
CЛЕДУЕТ ПОМНИТЬ:
44. Размещайте независимый от параметров код вне шаблонов.
CЛЕДУЕТ ПОМНИТЬ:
45. Разрабатывайте шаблоны функций-членов так, чтобы они принимали "все совместимые типы".
CЛЕДУЕТ ПОМНИТЬ:
46. Определяйте внуrри шаблонов функции, не являющиеся членами, когда желательны преобразования типа.
CЛЕДУЕТ ПОМНИТЬ:
47. Используйте классы-характеристики для предоставления информации о типах.
CЛЕДУЕТ ПОМНИТЬ:
CЛЕДУЕТ ПОМНИТЬ:
— Закрытое наследование означает "реализован посредством".
Обычно этот вариант хуже композиции, но все же приобретает смысл, когда производный класс нуждается в доступе к защищенным членам базового класса или должен переопределять унаследованные виртуальные функции.
— В отличие от композиции, закрытое наследование позволяет проводить оптимизацию пустого базового класса.
Это может оказаться важным для разработчиков библиотек, которые стремятся минимизировать размеры объектов.
40. Продумывайте подход к использованию множественного наследования.
CЛЕДУЕТ ПОМНИТЬ:
— Множественное наследование сложнее одиночного.
Оно может привести к неоднозначности и необходимости применять виртуальное наследование.
— Цена виртуального наследования - дополнительные затраты памяти, снижение быстродействия и усложнение операций инициализации и присваивания.
На практике его разумно применять, когда виртуальные базовые классы не содержат данных.
— Множественное наследование вполне законно.
Один из сценариев включает комбинацию открытого наследования интерфейсного класса и закрытого наследования класса, помогающего в реализации.
41. Разберитесь в том, что такое неявные интерфейсы и полиморфизм на этапе компиляции.
CЛЕДУЕТ ПОМНИТЬ:
— И классы, и шаблоны поддерживают интерфейсы и полиморфизм.
— Для классов интерфейсы определены явно и включают главным образом сигнатуры функций.
Полиморфизм проявляется во время исполнения - через виртуальные функции.
— Для параметров шаблонов интерфейсы неявны и основаны на корректных выражениях.
Полиморфизм проявляется во время компиляции - через конкретизацию и разрешение перегрузки функций.
42. Усвойте оба значения ключевого слова typename.
CЛЕДУЕТ ПОМНИТЬ:
— В объявлениях параметров шаблона ключевые слова class и typename взаимозаменяемы.
— Используйте typename для идентификации вложенных зависимых имен типов, если они не встречаются в списке базовых классов или в качестве идентификатора базового класса в списках инициализации членов.
43. Необходимо знать, как обращаться к именам в шаблонных базовых классах.
CЛЕДУЕТ ПОМНИТЬ:
— В шаблонах производных классов ссылки на имена из шаблонов базовых классов осуществляются с помощью префикса "this->". using-объявления либо посредством явного указания базового класса.
44. Размещайте независимый от параметров код вне шаблонов.
CЛЕДУЕТ ПОМНИТЬ:
— Шаблоны генерируют множество классов и функций, поэтому любой встречающийся в шаблоне код, который не зависит от параметров шаблона, приводит к разбуханию кода.
— Разбухания из-за параметров шаблонов, не являющихся типами, часто можно избежать, заменив параметры шаблонов параметрами функций или данными-членами класса.
— Разбухание из-за параметров-типов можно ограничить, обеспечив общие реализации для случаев, когда шаблон конкретизируется типами с одинаковым двоичным представлением.
45. Разрабатывайте шаблоны функций-членов так, чтобы они принимали "все совместимые типы".
CЛЕДУЕТ ПОМНИТЬ:
— Используйте шаблонные функции-члены для генерации функций, принимающих все совместимые типы.
— Если вы объявляете шаблоны обобщенных конструкторов копирования или обобщенного оператора присваивания, то по-прежнему должны объявить обычный конструктор копирования и оператор присваивания.
46. Определяйте внуrри шаблонов функции, не являющиеся членами, когда желательны преобразования типа.
CЛЕДУЕТ ПОМНИТЬ:
— Когда пишете шаблон класса, в котором есть функции, нуждающиеся в неявных преобразованиях типа для всех параметров, определяйте такие функции как друзей внутри шаблона класса.
47. Используйте классы-характеристики для предоставления информации о типах.
CЛЕДУЕТ ПОМНИТЬ:
— Классы-характеристики делают доступной информацию о типах во время компиляции.
Они реализованы с применением шаблонов и их специализаций.
— В сочетании с перегрузкой классы-характеристики позволяют проверять типы во время компиляции.
48. Изучите метапрограммирование шаблонов.
CЛЕДУЕТ ПОМНИТЬ:
49. Разберитесь в поведении обработчика new.
CЛЕДУЕТ ПОМНИТЬ:
50. Когда имеет смысл заменять new и delete.
CЛЕДУЕТ ПОМНИТЬ:
51. Придерживайтесь принятых соглашений при написании new и delete.
CЛЕДУЕТ ПОМНИТЬ:
52. Если вы написали оператор new с размещением, напишите и соответствующий оператор delete.
CЛЕДУЕТ ПОМНИТЬ:
53. Обращайте внимание на предупреждения компилятора.
CЛЕДУЕТ ПОМНИТЬ:
54. Ознакомьтесь со стандартной библиотекой, включая TR1.
CЛЕДУЕТ ПОМНИТЬ:
55. Познакомьтесь с Boost.
CЛЕДУЕТ ПОМНИТЬ:
CЛЕДУЕТ ПОМНИТЬ:
— Метапрограммирование шаблонов позволяет перенести часть работы со стадии исполнения на стадию компиляции.
За счет этого можно раньше обнаружить ошибки и повысить производительность программ.
— Технология ТМР может быть использована для генерации кода на основе комбинации политик, а также чтобы предотвратить генерацию кода, некорректного для определенных типов данных.
49. Разберитесь в поведении обработчика new.
CЛЕДУЕТ ПОМНИТЬ:
— set_new_handler позволяет указать функцию, которая должна быть вызвана, если запрос на выделение памяти не может быть удовлетворен.
— Полезность nothrow new ограничена, поскольку эта форма применима только для выделения памяти;
последующие вызовы конструктора могут по-прежнему возбуждать исключения.
50. Когда имеет смысл заменять new и delete.
CЛЕДУЕТ ПОМНИТЬ:
— Есть много причин для написания специальных версий new и delete, включая повышение производительности, отладку ошибок при работе с кучей, а также сбор информации об использовании памяти.
51. Придерживайтесь принятых соглашений при написании new и delete.
CЛЕДУЕТ ПОМНИТЬ:
— Оператор new должен содержать бесконечный цикл, который пытается выделить память, должен вызывать функцию-обработчик new, если не удается удовлетворить запрос на выделение памяти, и должен обрабатывать запрос на выделение нуля байтов.
Версии оператора new уровня класса должны обрабатывать запросы на выделение блоков большего размера, чем ожидается.
— Оператор delete не должен ничего делать при передаче ему нулевого указателя.
Версии оператора delete уровня класса должны обрабатывать запросы на освобождение блоков, которые больше, чем ожидается.
52. Если вы написали оператор new с размещением, напишите и соответствующий оператор delete.
CЛЕДУЕТ ПОМНИТЬ:
— Когда пишете размещающую версию оператора new, убедитесь, что не забыли о соответственном размещающем операторе delete.
Если его не будет, то в программе могут возникать тонкие, трудноуловимые утечки памяти.
— Объявляя размещающие версии new и delete, позаботьтесь о том, чтобы нечаянно не скрыть нормальных версий этих функций.
53. Обращайте внимание на предупреждения компилятора.
CЛЕДУЕТ ПОМНИТЬ:
— Принимайте всерьез предупреждения компилятора и старайтесь добиться того, чтобы ваш код вообще не вызывал предупреждений, даже при задании максимального уровня диагностики.
— Не впадайте в зависимость от предупреждений компилятора, потому что разные компиляторы предупреждают о разных вещах.
При переходе на новый компилятор могут пропасть некоторые предупреждения, на которые вы привыкли полагаться.
54. Ознакомьтесь со стандартной библиотекой, включая TR1.
CЛЕДУЕТ ПОМНИТЬ:
— Основная функциональность стандартной библиотеки С++ состоит из STL, потоков iostream и локалей. Также включена стандартная библиотека С99.
— TR1 добавляет поддержку "интеллектуальных" указателей (например, tr1::shared_ptr), обобщенных указателей на функции (tr1::function), кэшированных контейнеров, регулярных выражений и еще 10 компонентов.
— Отчет TR1 сам по себе - всеrо лишь спецификация.
Чтобы воспользоваться преимуществами TR1, понадобится реализация. Одним из источников реализаций компонентов TR1 является проект Boost.
55. Познакомьтесь с Boost.
CЛЕДУЕТ ПОМНИТЬ:
— Boost - сообщество и WеЬ-сайт для разработки бесплатных библиотек на С++ с открытыми исходными текстами, подверrающихся публичному обсуждению.
Boost оказывает немалое влияние на процедуру стандартизации С++.
— Boost предоставляет реализацию многих компонентов TR1, но - кроме того - и множество друrих библиотек.
#505_C_Cpp_GO_RUST_NTWK
Веб сервер, что это такое, для чего он нужен, какие задачи должен выполнять?
Примеры веб - серверов на С, С++, Rust, Golang.
Веб-сервер — что это?
Веб-сервер — программа или система программного обеспечения, которая принимает запросы от клиентов (обычно браузеров), обрабатывает их и возвращает запрошенные ресурсы (например, HTML-документы, изображения, файлы JavaScript и CSS).
Основная задача веб-сервера заключается в обеспечении обмена информацией между клиентом и сервером посредством протокола HTTP/HTTPS.
Основные задачи, выполняемые веб-сервером:
Приём запросов — получение входящего запроса от клиента (браузера).
Обработка запросов — проверка формата запроса, авторизация пользователя, определение запрашиваемого ресурса.
Генерация ответов — формирование содержимого страницы, обработка динамических страниц, отправка статического контента (HTML-файлы, изображения, скрипты).
Отправка ответов клиенту — передача сформированного ответа обратно клиенту согласно протоколу HTTP/HTTPS.
Безопасность — обеспечение защиты сервера и передаваемых данных, аутентификация пользователей, шифрование передачи данных (SSL/TLS).
Логирование — запись действий и ошибок в журнал для последующего анализа и устранения проблем.
Управление нагрузкой — распределение нагрузки среди нескольких серверов (балансировка нагрузки), кэширование часто используемых ресурсов для повышения производительности.
Оптимизация скорости — сжатие файлов перед передачей, использование асинхронности обработки запросов, кеширование результатов запросов.
Пример простого веб-сервера на C:
Этот пример демонстрирует простой веб-сервер на языке C, который слушает порт 8080, принимает входящие соединения и возвращает простейшую страницу с текстом "Hello from C Web Server!".
Веб сервер, что это такое, для чего он нужен, какие задачи должен выполнять?
Примеры веб - серверов на С, С++, Rust, Golang.
Веб-сервер — что это?
Веб-сервер — программа или система программного обеспечения, которая принимает запросы от клиентов (обычно браузеров), обрабатывает их и возвращает запрошенные ресурсы (например, HTML-документы, изображения, файлы JavaScript и CSS).
Основная задача веб-сервера заключается в обеспечении обмена информацией между клиентом и сервером посредством протокола HTTP/HTTPS.
Основные задачи, выполняемые веб-сервером:
Приём запросов — получение входящего запроса от клиента (браузера).
Обработка запросов — проверка формата запроса, авторизация пользователя, определение запрашиваемого ресурса.
Генерация ответов — формирование содержимого страницы, обработка динамических страниц, отправка статического контента (HTML-файлы, изображения, скрипты).
Отправка ответов клиенту — передача сформированного ответа обратно клиенту согласно протоколу HTTP/HTTPS.
Безопасность — обеспечение защиты сервера и передаваемых данных, аутентификация пользователей, шифрование передачи данных (SSL/TLS).
Логирование — запись действий и ошибок в журнал для последующего анализа и устранения проблем.
Управление нагрузкой — распределение нагрузки среди нескольких серверов (балансировка нагрузки), кэширование часто используемых ресурсов для повышения производительности.
Оптимизация скорости — сжатие файлов перед передачей, использование асинхронности обработки запросов, кеширование результатов запросов.
Пример простого веб-сервера на C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8080
void handle_client(int client_socket) {
char buffer[1024];
ssize_t bytes_read = recv(client_socket, buffer, sizeof(buffer)-1, 0);
if(bytes_read > 0) {
buffer[bytes_read] = '\0';
printf("Received request:\n%s\n", buffer);
// Send a simple response back to the client
const char* response =
"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"
"<html><body><h1>Hello from C Web Server!</h1></body></html>";
send(client_socket, response, strlen(response), 0);
}
close(client_socket); // Close connection after sending response
}
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
// Create socket file descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// Forcefully attaching socket to port 8080
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// Bind the socket to the specified IP and port
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0 ) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// Listen for incoming connections
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
while(1) { // Accept multiple clients
if ((new_socket = accept(server_fd, (struct sockaddr *)NULL, NULL)) < 0) {
perror("accept");
continue;
}
handle_client(new_socket);
}
return 0;
}
Этот пример демонстрирует простой веб-сервер на языке C, который слушает порт 8080, принимает входящие соединения и возвращает простейшую страницу с текстом "Hello from C Web Server!".
Пример простого веб-сервера на C++:
Здесь представлен веб-сервер на C++, также возвращающий простую HTML-страницу с сообщением "Hello from C++!". Сервер работает на порте 8080.
Пример простого веб-сервера на Rust:
Rust является популярным языком для разработки высокопроизводительных приложений благодаря своей безопасности памяти и эффективности исполнения.
Для написания веб-сервера в Rust удобно воспользоваться библиотекой actix-web:
Этот код создает простое приложение с использованием фреймворка Actix Web, которое прослушивает адрес localhost:8080 и выводит сообщение "Hello from Rust!" при обращении по адресу /.
Пример простого веб-сервера на Go (Golang):
Go отлично подходит для быстрого запуска простых и эффективных веб-сервисов.
Простой пример на Go выглядит следующим образом:
Данный скрипт запускает простой веб-сервер, доступный по адресу localhost:8080. При заходе на главную страницу возвращается простая строка "Hello from Go!".
Веб-серверы являются основой современных интернет-приложений и обеспечивают взаимодействие клиентских устройств с ресурсами, размещёнными на серверах.
Выбор языка зависит от требований проекта: C/C++ подойдут для низкоуровневых решений, Rust обеспечит безопасность памяти и высокую производительность, Go удобен для быстрой разработки и масштабирования.
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <string>
#include <arpa/inet.h>
using namespace std;
const string default_response =
"RFC 2616 compliant HTTP Response\r\n"
"HTTP/1.1 200 OK\r\n"
"Server: Simple C++ Web Server\r\n"
"Content-Length: 26\r\n"
"\r\n"
"<h1>Hello from C++!</h1>\r\n";
// Handle incoming requests
void handleRequest(int sockfd) {
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
ssize_t n = read(sockfd, buffer, sizeof(buffer)-1);
cout << "Client Request:" << endl << buffer << endl;
write(sockfd, default_response.c_str(), default_response.length());
close(sockfd);
}
int main() {
int listenfd, connfd;
struct sockaddr_in servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(8080);
bind(listenfd, reinterpret_cast<const sockaddr*>(&servaddr), sizeof(servaddr));
listen(listenfd, 10);
while(true) {
connfd = accept(listenfd, nullptr, nullptr);
handleRequest(connfd);
}
return 0;
}
Здесь представлен веб-сервер на C++, также возвращающий простую HTML-страницу с сообщением "Hello from C++!". Сервер работает на порте 8080.
Пример простого веб-сервера на Rust:
Rust является популярным языком для разработки высокопроизводительных приложений благодаря своей безопасности памяти и эффективности исполнения.
Для написания веб-сервера в Rust удобно воспользоваться библиотекой actix-web:
use actix_web::{web, App, HttpServer, Responder};
async fn index() -> impl Responder {
"Hello from Rust!"
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
println!("Starting web server on http://localhost:8080");
HttpServer::new(|| {
App::new().route("/", web::get().to(index))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}Этот код создает простое приложение с использованием фреймворка Actix Web, которое прослушивает адрес localhost:8080 и выводит сообщение "Hello from Rust!" при обращении по адресу /.
Пример простого веб-сервера на Go (Golang):
Go отлично подходит для быстрого запуска простых и эффективных веб-сервисов.
Простой пример на Go выглядит следующим образом:
package main
import (
"fmt"
"log"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello from Go!")
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Данный скрипт запускает простой веб-сервер, доступный по адресу localhost:8080. При заходе на главную страницу возвращается простая строка "Hello from Go!".
Веб-серверы являются основой современных интернет-приложений и обеспечивают взаимодействие клиентских устройств с ресурсами, размещёнными на серверах.
Выбор языка зависит от требований проекта: C/C++ подойдут для низкоуровневых решений, Rust обеспечит безопасность памяти и высокую производительность, Go удобен для быстрой разработки и масштабирования.
#506_C_PRGM
Программка для перевода строки представляющей шестнадцатиричное число в строковом представлении в строку двоичных кодов. Т.е. нам задается шестнадцатиричное число в виде 0x1234567890abcdf или 0X12fde... или fffde4532a1eb. Длина входной строки до 100 символов, а на выходе мы имеем двоичное представление данного числа.
Не захотел использовать массив с представлением каждой шестнадцатиричной цифры и выполнил преобразование с помощью битовых операций.
Если у кого есть идеи по оптимизации с радостью выслушаю.
Программка для перевода строки представляющей шестнадцатиричное число в строковом представлении в строку двоичных кодов. Т.е. нам задается шестнадцатиричное число в виде 0x1234567890abcdf или 0X12fde... или fffde4532a1eb. Длина входной строки до 100 символов, а на выходе мы имеем двоичное представление данного числа.
Не захотел использовать массив с представлением каждой шестнадцатиричной цифры и выполнил преобразование с помощью битовых операций.
// hex_to_bin.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
void digit_to_bin(const char digit) {
int cp_digit;
if (digit > 47 && digit < 58) {
cp_digit = digit - 48;
} else if (digit > 96 && digit < 103) {
cp_digit = digit - 87;
} else {
perror("Incorrect input");
exit(0);
}
for (int i = 3; i >= 0; --i) {
printf("%d", (cp_digit >> i) & 1);
}
}
void hex_to_bin(const char* hex_str_num) {
for (int i = 0; i != strlen(hex_str_num); ++i) {
digit_to_bin(tolower(hex_str_num[i]));
}
putchar('\n');
}
void read_line(char* buffer, size_t buffer_size) {
const char* input = fgets(buffer, buffer_size, stdin);
if (NULL != input) {
char* end_ptr = strchr(buffer, '\n');
if (NULL != end_ptr) {
*end_ptr = '\0';
}
}
}
char* prefix_ignore(char* input) {
char* result = NULL;
if (input[0] == '0' && tolower(input[1]) == 'x') {
result = &input[2];
}
return result;
}
int main(void) {
enum {SIZE = 101};
char input[SIZE] = {0};
read_line(input, SIZE);
//puts(input);
if (strlen(input)) {
const char* hex_ptr = prefix_ignore(input);
if (NULL != hex_ptr) {
hex_to_bin(prefix_ignore(input));
} else {
hex_to_bin(input);
}
}
return 0;
}
Если у кого есть идеи по оптимизации с радостью выслушаю.
#507_PPPO_TP
Что такое динамические классы?
Динамическими классы — механизм в ООП, позволяющий создавать новые типы классов во время исполнения программы, а также изменять поведение существующих классов и объектов без перекомпиляции исходного кода.
Это отличается от статического подхода, когда структура классов фиксируется заранее, на этапе компиляции, и неизменна в процессе выполнения программы.
Примеры реализации динамических классов.
Python поддерживает создание классов динамически благодаря встроенным инструментам метаклассов (type(), __new__):
JavaScript позволяет переопределять методы объекта даже после его создания:
Ruby широко известен своей поддержкой изменения поведения классов во время выполнения («метапрограммирование»):
Преимущества динамических классов:
Гибкость — возможность адаптации приложения к новым требованиям прямо во время работы.
Расширяемость — простое внедрение новых типов и методов без остановки системы.
Простота тестирования — легче проверять и экспериментировать с новыми классами.
Важно помнить, что чрезмерное использование динамического создания классов усложняет поддержку и понимание кода, особенно для новичков.
Что такое динамические классы?
Динамическими классы — механизм в ООП, позволяющий создавать новые типы классов во время исполнения программы, а также изменять поведение существующих классов и объектов без перекомпиляции исходного кода.
Это отличается от статического подхода, когда структура классов фиксируется заранее, на этапе компиляции, и неизменна в процессе выполнения программы.
Примеры реализации динамических классов.
Python поддерживает создание классов динамически благодаря встроенным инструментам метаклассов (type(), __new__):
# Динамическое создание класса Person
Person = type('Person', (), {'name': 'John'})
john = Person()
print(john.name)
JavaScript позволяет переопределять методы объекта даже после его создания:
let obj = {};
obj.sayHello = function () {
console.log("Привет!");
};
obj.sayHello();Ruby широко известен своей поддержкой изменения поведения классов во время выполнения («метапрограммирование»):
class Animal
def speak
puts "Р-р!"
end
end
Animal.class_eval do
define_method(:roar) { |volume| puts "#{volume} Р-Р-Р!!!" }
end
lion = Animal.new
lion.roar("Громко")
Преимущества динамических классов:
Гибкость — возможность адаптации приложения к новым требованиям прямо во время работы.
Расширяемость — простое внедрение новых типов и методов без остановки системы.
Простота тестирования — легче проверять и экспериментировать с новыми классами.
Важно помнить, что чрезмерное использование динамического создания классов усложняет поддержку и понимание кода, особенно для новичков.
#508_Cpp_GO_RUST_PPPO_TP
Динамические классы или аналоги в С++, Rust, Golang.
В ЯП C++, Rust и Golang отсутствуют механизмы прямого создания динамических классов аналогично Python или Ruby, поскольку они являются языками с сильной типизацией и строгими правилами компиляции.
Однако существуют альтернативные подходы, позволяющие реализовать похожее поведение, хотя и с ограничениями.
C++: Хотя создание классов во время выполнения невозможно, можно воспользоваться несколькими подходами для достижения гибкости:
Методы расширения функциональности:
Шаблоны — генерация семейств классов на стадии компиляции.
Наследование и полиморфизм — создание иерархий классов с виртуальными методами.
RTTI (Run-time Type Information) — получение информации о классе во время выполнения (например, оператор dynamic_cast).
Фреймворки для Reflection — библиотеки вроде libclang позволяют анализировать структуру классов на уровне бинарников.
Пример использования шаблона для динамической обработки разных типов:
Rust — язык с безопасной моделью памяти и отсутствием наследования в традиционном смысле.
Вместо него используются структуры и трейт-интерфейсы.
Основные подходы:
Traits (черты) — определение интерфейса для структур, позволяющего применять различные реализации методов к разным типам.
Generics (обобщенные типы) — шаблонная система позволяет создавать универсальные алгоритмы и контейнеры, работающие с разными типами данных.
Boxed traits — использование динамического диспетчеринга (аналог виртуальных функций), где поведение выбирается на этапе выполнения.
Пример использования trait и boxed trait:
Golang — имеет простую и минималистичную систему типов и наследования.
Поддерживает интерфейсное программирование, что делает возможным частичное достижение динамичности:
Интерфейс — описывает набор методов, которыми должен обладать объект, поддерживающий этот интерфейс.
Отражение (Reflection) — пакет reflect позволяет получать информацию о типах переменных во время выполнения и вызывать методы динамически.
Пример использования интерфейса:
Использование отражения (reflection):
Таким образом, несмотря на отсутствие полноценного механизма динамических классов, в каждом из указанных языков существует ряд подходов, позволяющих достичь требуемого уровня гибкости и расширяемости приложений.
Динамические классы или аналоги в С++, Rust, Golang.
В ЯП C++, Rust и Golang отсутствуют механизмы прямого создания динамических классов аналогично Python или Ruby, поскольку они являются языками с сильной типизацией и строгими правилами компиляции.
Однако существуют альтернативные подходы, позволяющие реализовать похожее поведение, хотя и с ограничениями.
C++: Хотя создание классов во время выполнения невозможно, можно воспользоваться несколькими подходами для достижения гибкости:
Методы расширения функциональности:
Шаблоны — генерация семейств классов на стадии компиляции.
Наследование и полиморфизм — создание иерархий классов с виртуальными методами.
RTTI (Run-time Type Information) — получение информации о классе во время выполнения (например, оператор dynamic_cast).
Фреймворки для Reflection — библиотеки вроде libclang позволяют анализировать структуру классов на уровне бинарников.
Пример использования шаблона для динамической обработки разных типов:
template <typename T>
void process(T value) {
std::cout << "Обработка типа: " << typeid(value).name() << "\n";
}
process<int>(42); // Обработаем целое число
process<double>(3.14); // Обработаем вещественное число
Rust — язык с безопасной моделью памяти и отсутствием наследования в традиционном смысле.
Вместо него используются структуры и трейт-интерфейсы.
Основные подходы:
Traits (черты) — определение интерфейса для структур, позволяющего применять различные реализации методов к разным типам.
Generics (обобщенные типы) — шаблонная система позволяет создавать универсальные алгоритмы и контейнеры, работающие с разными типами данных.
Boxed traits — использование динамического диспетчеринга (аналог виртуальных функций), где поведение выбирается на этапе выполнения.
Пример использования trait и boxed trait:
trait Shape {
fn area(&self) -> f64;
}
struct Circle(f64);
impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.0.powi(2)
}
}
fn print_area(shape: &dyn Shape) {
println!("Площадь фигуры: {}", shape.area());
}
// Используем circle
let c = Circle(3.0);
print_area(&c);Golang — имеет простую и минималистичную систему типов и наследования.
Поддерживает интерфейсное программирование, что делает возможным частичное достижение динамичности:
Интерфейс — описывает набор методов, которыми должен обладать объект, поддерживающий этот интерфейс.
Отражение (Reflection) — пакет reflect позволяет получать информацию о типах переменных во время выполнения и вызывать методы динамически.
Пример использования интерфейса:
package main
import "fmt"
type Shaper interface {
Area() float64
}
type Rectangle struct {
width, height float64
}
func (r Rectangle) Area() float64 {
return r.width * r.height
}
func main() {
r := Rectangle{width: 10, height: 5}
fmt.Println(r.Area()) // Площадь прямоугольника
}
Использование отражения (reflection):
package main
import (
"fmt"
"reflect"
)
type MyStruct struct{}
func (m MyStruct) SayHi() string {
return "Привет!"
}
func main() {
myInstance := MyStruct{}
methodValue := reflect.ValueOf(myInstance).MethodByName("SayHi").Call(nil)[0]
fmt.Println(methodValue.String())
}
Таким образом, несмотря на отсутствие полноценного механизма динамических классов, в каждом из указанных языков существует ряд подходов, позволяющих достичь требуемого уровня гибкости и расширяемости приложений.
#509_ADM_DvOp_NTWK
Если сервер не отвечает, как можно получить доступ к серверу, не находясь непосредственно в ЦОДе?
Cуществует два варианта:
KVM (Keyboard, Video & Mouse) — аппаратное средство удаленного управления компьютером через подключение клавиатуры, монитора и мыши.
Позволяет получать доступ даже тогда, когда ОС сервера зависла или вообще не загружается.
IPMI (Intelligent Platform Management Interface) — интерфейс удалённого администрирования, позволяющий управлять оборудованием сервера независимо от состояния операционной системы.
Через IPMI также можно перезагрузить сервер, изменить настройки BIOS и многое другое.
Если сервер не отвечает, как можно получить доступ к серверу, не находясь непосредственно в ЦОДе?
Cуществует два варианта:
KVM (Keyboard, Video & Mouse) — аппаратное средство удаленного управления компьютером через подключение клавиатуры, монитора и мыши.
Позволяет получать доступ даже тогда, когда ОС сервера зависла или вообще не загружается.
IPMI (Intelligent Platform Management Interface) — интерфейс удалённого администрирования, позволяющий управлять оборудованием сервера независимо от состояния операционной системы.
Через IPMI также можно перезагрузить сервер, изменить настройки BIOS и многое другое.
#510_ADM_DvOp_TOS
Что такое inode?
При помощи какой команды можно посмотреть номер inode файла?
Inode (от англ. index node) — структура данных файловых систем Unix-подобных ОС, содержащая метаданные файла или каталога.
Проще говоря, каждый файл и каталог в UNIX имеет связанный с ним уникальный номер inode, содержащий важную информацию о файле, такую как права доступа, владелец, размер, время последнего изменения и расположения блоков данных на диске.
Основные компоненты inode включают:
— Номер inode (идентификатор).
— Тип объекта (обычный файл, директорию, символьную ссылку и др.).
— Права доступа и владельца.
— Временные отметки (дата создания, модификации, последнего доступа).
— Размер файла и количество ссылок (hard links).
— Информация о расположении данных (блоки хранения на устройстве).
Файловые системы отслеживают файлы и папки через inodes, а не по названиям файлов.
Когда вы обращаетесь к файлу по имени, ядро сначала ищет соответствующий inode, а затем извлекает необходимые данные.
Примеры команд для работы с inode:
— Показать список файлов вместе с номерами inode:
— Найти файлы по номеру inode:
Понимание концепции inode полезно при работе с системой и диагностике проблем, связанных с хранением и организацией файлов.
Команда stat — отображает подробную информацию о файле, включая его номер inode.
Пример использования:
Результат покажет много деталей о файле, среди которых будет и поле "Inode" с соответствующим значением.
Что такое inode?
При помощи какой команды можно посмотреть номер inode файла?
Inode (от англ. index node) — структура данных файловых систем Unix-подобных ОС, содержащая метаданные файла или каталога.
Проще говоря, каждый файл и каталог в UNIX имеет связанный с ним уникальный номер inode, содержащий важную информацию о файле, такую как права доступа, владелец, размер, время последнего изменения и расположения блоков данных на диске.
Основные компоненты inode включают:
— Номер inode (идентификатор).
— Тип объекта (обычный файл, директорию, символьную ссылку и др.).
— Права доступа и владельца.
— Временные отметки (дата создания, модификации, последнего доступа).
— Размер файла и количество ссылок (hard links).
— Информация о расположении данных (блоки хранения на устройстве).
Файловые системы отслеживают файлы и папки через inodes, а не по названиям файлов.
Когда вы обращаетесь к файлу по имени, ядро сначала ищет соответствующий inode, а затем извлекает необходимые данные.
Примеры команд для работы с inode:
— Показать список файлов вместе с номерами inode:
ls -i
— Найти файлы по номеру inode:
find /path/to/directory -inum <номер_inode> -print
Понимание концепции inode полезно при работе с системой и диагностике проблем, связанных с хранением и организацией файлов.
Команда stat — отображает подробную информацию о файле, включая его номер inode.
Пример использования:
stat file_name
Результат покажет много деталей о файле, среди которых будет и поле "Inode" с соответствующим значением.
#511_ADM_DvOp_TOS
Как узнать номер у процесса SIGKILL?
Как узнать номер процесса systemd?
Номер сигнала SIGKILL в ОС Linux — 9.
Этот сигнал используется для немедленного завершения процесса без возможности обработки самим процессом.
Процесс не может перехватывать или игнорировать этот сигнал, поэтому он гарантированно завершит процесс.
Для проверки номера сигнала в ОС Linux можно воспользоваться командой:
или посмотреть полный список сигналов командой:
Также можно обратиться к заголовочному файлу <signal.h>, содержащему определения всех стандартных сигналов системы.
Как узнать номер процесса systemd?
Способ 1: Через команду ps:
Получаем список процессов, связанных с системой инициализации, среди которых первым должен идти процесс systemd. Его PID находится в первом столбце.
Пример вывода команды:
Здесь первый столбец — это именно PID.
Способ 2: Через утилиту pidof, которая сразу выдаст нужный вам PID:
Эта команда выводит PID основного процесса systemd.
Таким образом, самый простой способ быстро посмотреть PID процесса systemd — использовать команду pidof systemd.
Как узнать номер у процесса SIGKILL?
Как узнать номер процесса systemd?
Номер сигнала SIGKILL в ОС Linux — 9.
Этот сигнал используется для немедленного завершения процесса без возможности обработки самим процессом.
Процесс не может перехватывать или игнорировать этот сигнал, поэтому он гарантированно завершит процесс.
Для проверки номера сигнала в ОС Linux можно воспользоваться командой:
kill -l | grep SIGKILL
или посмотреть полный список сигналов командой:
man 7 signal
Также можно обратиться к заголовочному файлу <signal.h>, содержащему определения всех стандартных сигналов системы.
Как узнать номер процесса systemd?
Способ 1: Через команду ps:
ps aux | grep systemd
Получаем список процессов, связанных с системой инициализации, среди которых первым должен идти процесс systemd. Его PID находится в первом столбце.
Пример вывода команды:
root 1 0.0 0.0 7880 1804 ? Ss Aug11 0:01 /usr/lib/systemd/systemd --switch-root=/run/initramfs/initrd.img
...
Здесь первый столбец — это именно PID.
Способ 2: Через утилиту pidof, которая сразу выдаст нужный вам PID:
pidof systemd
Эта команда выводит PID основного процесса systemd.
Таким образом, самый простой способ быстро посмотреть PID процесса systemd — использовать команду pidof systemd.
#512_ADM_DvOp_TOS
Что такое systemd в Linux?
Systemd — система инициализации и управления сервисами, разработанная для ОС семейства Linux.
Она заменила собой традиционные системы запуска служб типа SysV init и Upstart, став основой большинства современных дистрибутивов Linux, включая Ubuntu, Debian, Fedora, CentOS и многие другие.
Основные возможности Systemd.
Инициализация системы — управление запуском сервисов и демонов при загрузке операционной системы.
Управление устройствами — отслеживание состояния устройств и запуск соответствующих драйверов и приложений при подключении новых аппаратных компонентов.
Журналирование — ведение журналов событий и сообщений от различных служб и приложений, что упрощает диагностику проблем.
Контроль ресурсов — возможность ограничения потребления ресурсов (процессора, памяти, ввода-вывода) отдельными приложениями или группами процессов.
Мониторинг зависимостей — автоматическое управление последовательностью загрузки и запуска служб, обеспечивая правильное выполнение зависимых друг от друга операций.
Параллельный запуск сервисов — благодаря поддержке параллельного запуска нескольких сервисов одновременно, ускоряется общая скорость загрузки ОС.
Автоматический перезапуск — служба автоматически пытается восстановить работу приложения, если оно завершилось аварийно.
Структура Systemd.
Система построена вокруг трех основных понятий:
Единицы (units) — абстрактное представление любого ресурса, управляемого systemd (например, службы, устройства, монтирования файловых систем).
Цель (target) — группа единиц, объединенных общей задачей (например, сеть, графическая среда).
Демон (daemon) — основной исполняемый процесс systemd, который управляет всеми единицами и целями.
Для взаимодействия с systemd используются различные команды:
Запустить службу:
Остановить службу:
Перезагрузить систему:
Проверить статус сервиса:
Отключить автозапуск службы:
Несмотря на критику из-за сложности и объема функций, systemd стала стандартом для многих крупных дистрибутивов Linux благодаря своей универсальности и эффективности.
Что такое systemd в Linux?
Systemd — система инициализации и управления сервисами, разработанная для ОС семейства Linux.
Она заменила собой традиционные системы запуска служб типа SysV init и Upstart, став основой большинства современных дистрибутивов Linux, включая Ubuntu, Debian, Fedora, CentOS и многие другие.
Основные возможности Systemd.
Инициализация системы — управление запуском сервисов и демонов при загрузке операционной системы.
Управление устройствами — отслеживание состояния устройств и запуск соответствующих драйверов и приложений при подключении новых аппаратных компонентов.
Журналирование — ведение журналов событий и сообщений от различных служб и приложений, что упрощает диагностику проблем.
Контроль ресурсов — возможность ограничения потребления ресурсов (процессора, памяти, ввода-вывода) отдельными приложениями или группами процессов.
Мониторинг зависимостей — автоматическое управление последовательностью загрузки и запуска служб, обеспечивая правильное выполнение зависимых друг от друга операций.
Параллельный запуск сервисов — благодаря поддержке параллельного запуска нескольких сервисов одновременно, ускоряется общая скорость загрузки ОС.
Автоматический перезапуск — служба автоматически пытается восстановить работу приложения, если оно завершилось аварийно.
Структура Systemd.
Система построена вокруг трех основных понятий:
Единицы (units) — абстрактное представление любого ресурса, управляемого systemd (например, службы, устройства, монтирования файловых систем).
Цель (target) — группа единиц, объединенных общей задачей (например, сеть, графическая среда).
Демон (daemon) — основной исполняемый процесс systemd, который управляет всеми единицами и целями.
Для взаимодействия с systemd используются различные команды:
Запустить службу:
sudo systemctl start <служба>.service
Остановить службу:
sudo systemctl stop <служба>.service
Перезагрузить систему:
sudo systemctl reboot
Проверить статус сервиса:
sudo systemctl status <служба>.service
Отключить автозапуск службы:
sudo systemctl disable <служба>.service
Несмотря на критику из-за сложности и объема функций, systemd стала стандартом для многих крупных дистрибутивов Linux благодаря своей универсальности и эффективности.