программирование
Закон дырявых абстракций
Статья 2000 года, каждый абзац которой можно разобрать на цитаты.
http://russian.joelonsoftware.com/Articles/LeakyAbstractions.html
TCP
Волшебство же состоит в том, что TCP основан на IP. Иными словами, TCP обязуется работать надёжно, используя лишь ненадёжные детали.
Погода
И ещё: несмотря на дворники, мощные фары, крышу и обогреватель, которые защищают (абстрагируют) от непогоды, под дождём быстро ехать нельзя; приходится иметь дело с водяной подушкой, а иногда ливень такой, что на дороге ничего не видно, и надо остановиться; так что и погоду, из-за закона дырявых абстракций, полностью не абстрагируешь.
С++
Интересно, что историю развития C++ можно описать как историю затыкания дырок в абстракции строк. Уж не знаю, отчего бы не добавить к языку элементарный класс строчек.
Ссылка или сам объект?
Вопрос
Я заметил, что нередко программисты, чьи коды я видел, используют указатели на объекты чаще, чем сами эти объекты, т. е. например, используют следующую конструкцию:
Object *myObject = new Object;
вместо:
Object myObject;
Аналогично с методами. Почему вместо этого:
myObject.testFunc();
мы должны писать вот это:
myObject->testFunc();
Я так понимаю, что это дает выигрыш в скорости, т. к. мы обращаемся напрямую к памяти. Верно? P.S. Я перешел с Java.
Ответ
Заметим кстати, что в Java указатели не используются в явном виде, т. е. программист не может в коде обратиться к объекту через указатель на него. Однако на деле в Java все типы, кроме базовых, являются указателями. Соответственно, и обращение к ним происходит по ссылке, хотя явно передать параметр по ссылке нельзя. И еще, на заметку, new в C++ и Java или C# — абсолютно разные вещи.
Для того, чтобы дать небольшое представление, что же такое указатели в C++, приведем два аналогичных фрагмента кода:
Java
Object object1 = new Object(); // Новый объект
Object object2 = new Object(); // Еще один новый объект
object1 = object2;
// Обе переменные ссылаются на объект, на который раньше ссылалась object2
// При изменении объекта, на который ссылается object1, изменится и
// object2, потому что это один и тот же объект
Ближайший эквивалент на C++
Object * object1 = new Object(); // Память выделена под новый объект
// На эту память ссылается object1
Object * object2 = new Object(); // Аналогично со вторым объектом
delete object1;
// В C++ нет системы сборки мусора, поэтому если этого не делать,
// к этой памяти программа уже не сможет получить доступ
// И, на самом деле, к ней уже никто не получит доступ,
// как минимум, до перезагрузки
object1 = object2; // Как и в Java, object1 указывает туда же, куда и object2
Однако вот это — совершенно другая вещь (C++):
Object object1; // Новый объект
Object object2; // Еще один
object1 = object2;
// Полное копирование объекта object2 в object1,
// а не переопределение указателя – очень дорогая операция
Я так понимаю, что это дает выигрыш в скорости, т. к. мы обращаемся напрямую к памяти.
На самом деле, совсем нет. Работа с указателями оформлена в виде кучи, в то время как работа с объектами — это стек, более простая и быстрая структура.
Строго говоря, этот вопрос объединяет в себе два различных. Первый: когда стоит использовать динамическое распределение памяти (new)? Второй: когда стоит использовать указатели? Естественно, здесь не обойдемся без общих слов о том, что всегда необходимо выбирать наиболее подходящий инструмент для работы. Почти всегда существует реализация лучше, чем с использованием ручного динамического распределения (dynamic allocation) или/и сырых указателей.
Динамическое распределение
В формулировке вопроса представлены два способа создания объекта. И основное различие заключается в сроке их жизни (storage duration) в памяти программы. Используя Object myObject;, вы полагаетесь на автоматическое определение срока жизни, и объект будет уничтожен сразу после выхода из его области видимости. А вот Object *myObject = new Object; сохраняет жизнь объекту до того момента, пока вы вручную не удалите его из памяти командой delete. Используйте последний вариант только тогда, когда это действительно необходимо. А потому всегда делайте выбор в пользу автоматического определения срока хранения объекта, если это возможно.
Обычно принудительное установления срока жизни применяется в следующих ситуациях:
- Вам необходимо, чтобы объект существовал и после выхода из области его видимости — именно этот объект, именно в этой области памяти, а не его копия. Если для вас это не принципиально (в большинстве случаев это так), положитесь на автоматическое определение срока жизни. Однако вот пример ситуации, когда вам может понадобиться обратить к объекту вне его области видимости, однако это можно сделать, не сохраняя его в явном виде: записав объект в вектор, вы можете «разорвать связь» с самим объектом — на самом деле он (а не его копия) будет доступен при вызове из вектора.
- Вам необходимо использовать много памяти, которая может переполнить стек. Здорово, если с такой проблемой не приходится сталкиваться (а с ней сталкиваются очень редко), потому что это «вне компетенции» C++, но к сожалению, иногда приходится решать и эту задачу.
- Вы, например, точно не знаете размер массива, который придется использовать. Как известно, в C++ массивы при определении имеют фиксированный размер. Это может вызвать проблемы, например, при считывании пользовательского ввода. Указатель же определяет только тот участок в памяти, куда будет записано начало массива, грубо говоря, не ограничивая его размер.
Если использование динамического распределения необходимо, то вам стоит инкапсулировать его с помощью умного указателя или другого поддерживающего идиому «Получение ресурса есть инициализация» типа (стандартные контейнеры ее поддерживают — это идиома, в соответствии с которой ресурс: блок памяти, файл, сетевое соединение и т. п. — при получении инициализируется в конструкторе, а затем аккуратно уничтожается деструктором). Умными являются, например, указатели std::unique_ptr и std::shared_ptr.
Указатели
Однако есть случаи, когда использование указателей оправдано не только с точки зрения динамического распределения памяти, но почти всегда есть альтернативный путь, без использования указателей, который вам и следует выбрать. Как и ранее, скажем: всегда делайте выбор в пользу альтернативы, если нет особенной необходимости в использовании указателей.
Случаями, когда использование указателей можно рассматривать как возможный вариант, можно назвать следующие:
- Ссылочная семантика. Иногда может быть необходимо обратиться к объекту (вне зависимости от того, как под него распределена память), поскольку вы хотите обратиться в функции именно в этому объекту, а не его копии — т. е. когда вам требуется реализовать передачу по ссылке. Однако в большинстве случаев, здесь достаточно использовать именно ссылку, а не указатель, потому что именно для этого ссылки и созданы. Заметьте, что это несколько разные вещи с тем, что описано в пункте 1 выше. Но если вы можете обратиться к копии объекта, то и ссылку использовать нет необходимости (но заметьте, копирование объекта — дорогая операция).
- Полиморфизм. Вызов функций в рамках полиморфизма (динамический класс объекта) возможен с помощью ссылки или указателя. И снова, использование ссылок более предпочтительно.
- Необязательный объект. В этом случае можно использовать nullptr, чтобы указать, что объект опущен. Если это аргумент функции, то лучше сделайте реализацию с аргументами по умолчанию или перегрузкой. С другой стороны, можно использовать тип, который инкапсулирует такое поведение, например boost::optional (измененный в C++14 std::optional).
- Повышение скорости компиляции. Вам может быть необходимо разделить единицы компиляции (compilation units). Одним из эффективных применений указателей является предварительная декларация (т. к. для использования объекта, вам необходимо предварительно его определить). Это позволить вам разнести единицы компиляции, что может положительно сказаться на ускорении времени компиляции, внушительно уменьшив время, затрачиваемое на этот процесс.
- Взаимодействие с библиотекой C или C-подобной. Здесь вам придется использовать сырые указатели, освобождение памяти из под которых вы производите в самый последний момент. Получить сырой указатель можно из умного указателя, например, операцией get. Если библиотека использует память, которая в последствии должна быть освобождена вручную, вы можете оформить деструктор в умном указателе.
Источник: stackoverflow.com
Перевод: vk.com
Лучшие примеры разработки ПО
Сборник интересных рассказов от разных умных чуваков из мира программирования.
Затронуты вопросы разработки, интерфейсов, аспектов языков программирования, эффективности труда и пр.
Легкое и полезное чтиво 2007 года издания.
A Byte of Python
Название книги — игра слов, переводимая как «укус питона».
Информативная и лёгкая книжка на 150 страниц, позволяющая заглянуть в мир языка программирования Python.
Читать документацию или толстые книги в 700 страниц быстро наскучит, а эту книжку читать приятно, после С++ на питоне просто натуральным образом отдыхаешь. Для таких как я, автор расставил по всей книге заметки о сравнении с другими языками программирования, иногда встречаются подколы в стиле: «То что вы сейчас прочитали ведь лучше С++, правда? :)».
Как улучшить свой стиль программирования?
Большой Секрет Еще Раз: программисты никому не нужны. И программы тоже. То есть совсем. Людям интересны они сами, их проблемы и некоторые другие люди. Просто так получилось, что часть проблем можно решить написанием кода. Если бы те же проблемы решались струганием деревянных дощечек и покраской их в зеленый цвет, и это было бы дешевле — так бы и делали.
Введение в ООП
Введение в объектно-ориентированное программирование от Володи Моженкова.
Лекции «Почти университета».
- Инкапсуляция
- Наследование
- Дочерний класс в памяти
- Агрегация и композиция
- Класс-обёртка
- Множественное наследование
- Анонимный объект
- Фабричный метод
- Абстрактная фабрика
- Простая фабрика
- Раннее и позднее связывание
- Анонимная функция и лямбда
- Functor или объект-функция (wiki)
- Двойное наследование в памяти
- Абстрактный класс
- Интерфейс в ООП
На десерт: Нужна ли математика программисту?
Структуры данных
Конечно, можно быть успешным программистом и без сакрального знания структур данных, однако они совершенно незаменимы в некоторых приложениях. Например, когда нужно вычислить кратчайший путь между двумя точками на карте, или найти имя в телефонной книжке, содержащей, скажем, миллион записей. Не говоря уже о том, что структуры данных постоянно используются в спортивном программировании.
Объектно-ориентированный анализ и проектирование
Фундаментальный труд про ООП.
Если в начале книги вы смутно представляете что же такое ООП и с чем его едят, то уже к 4 главе ваши мысли проясняются и вы можете уже придумывать абстракции, наследовать, агрегировать... и много других терминов, которые после первого прочтения книги вряд ли смогут уложиться в опухшей голове читателя.
Хорошая книга, наставляющая на путь объектно-ориентированного анализа.
Отдельно хочу отметить понятные и смешные иллюстрации, которыми автор немного разбавляет сложность излагаемой сути.
Программирование — не тяжелый физический труд, но все равно отстой
Вы эксперт во всех этих технологиях, и это хорошо, потому что ваша компетентность позволяет потратить всего лишь шесть часов на выяснение причин проблем, вместо потери работы. Теперь вы знаете еще один маленький факт в миллионах уже вам известных, потому что программы, от которых вы зависите, написаны козлами и идиотами.
Веревка достаточной длины, чтобы выстрелить себе в ногу
Хорошая книга в стиле «пара сотен советов от прожженого программиста».
Затрагиваются темы проектирования, проблем разработки, форматирования кода, названий переменных, использования препроцессора, различий языков С и С++. Последняя большая глава посвящена рассказу о том, как правильно пользоваться всем тем счастьем, которое сваливается на программиста С++.