Пишу о программировании, книгах, науке и жизни
3 заметки с тегом

c++

Точка с запятой

22 октября 2015, 1:24

Цитата неизвестного:

Существуют языки программирования, которые прекрасно обходятся без точки с запятой.

Точка с запятой — недосимвол. Это синтаксическая соль, дублирование символов конца строки. Сама по себе она заканчивает оператор, но с тем же успехом эту функцию могли бы выполнять символы конца строки. Использование точек с запятой везде — насилие над программистом.

Точка с запятой впервые появилась в Алголе‐58, во времена, когда символы переноса строки и возврата каретки считались несущественными и можно было лепить исходный код в однострочную нечитаемую мешанину. Однако же другие языки программирования прекрасно выживают без точек с запятой, им нет нужды дублировать символы конца строки, которые итак вставляет любой текстовый редактор по нажатию на Enter.

Все аргументы защитников повсеместного применения точек с запятой сводятся к тому, что так принято, так все привыкли, это архаизм древних языков 1958 года, который «невозможно» отменить. Мало того, в большинстве си‐подобных языков эта идиома намеренно введена в синтаксис и стандарт как обязательная. Теперь программист всегда должен выполнять лишнюю работу и ставить этот символ перед тем, как нажать на Enter.

Трудность даже не в том, что приходится каждый раз в конце строки ставить точку с запятой, эту задачу вполне могла бы решать интегрированная среда разработки или препроцессор. Трудность в том, что поставленная точка с запятой перед или после некоторых символов (например, «{», «}» в си или операторов «End» в паскалях) вызывает ошибку компиляции. В каком‐то месте кода точка с запятой обязательна, в каком‐то её можно опустить, а в каком‐то месте её вдруг ставить вообще нельзя — логику такого непоследовательного поведения объяснить невозможно, это можно только зазубрить.

Современные компиляторы вполне справляются с листингом, где для конца строк используются только два символа (с кодами 10 и 13), а не три, как в си и производных от него языках. Простой пример.
На VB.Net можно написать вот так:
Dim f = 32 / 4
Dim g = 25 / 5
Или вот так
Dim f =
32 / 4
Dim g = 25 / 5
Всё это будет валидным кодом. Компилятор сам умеет определять, является ли конец строки также и окончанием оператора, а если оператор не закончен на текущей строке, то он будет поглощать выражение из следующей строки.

Увы, в си такое поведение компилятора просто невозможно, потому что стандарт обязывает всегда указывать конец оператора, даже если он совпадает с концом строки.

c++

Ссылка или сам объект?

29 сентября 2015, 10:17

Вопрос

Я заметил, что нередко программисты, чьи коды я видел, используют указатели на объекты чаще, чем сами эти объекты, т. е. например, используют следующую конструкцию:

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. Используйте последний вариант только тогда, когда это действительно необходимо. А потому всегда делайте выбор в пользу автоматического определения срока хранения объекта, если это возможно.

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

  1. Вам необходимо, чтобы объект существовал и после выхода из области его видимости — именно этот объект, именно в этой области памяти, а не его копия. Если для вас это не принципиально (в большинстве случаев это так), положитесь на автоматическое определение срока жизни. Однако вот пример ситуации, когда вам может понадобиться обратить к объекту вне его области видимости, однако это можно сделать, не сохраняя его в явном виде: записав объект в вектор, вы можете «разорвать связь» с самим объектом — на самом деле он (а не его копия) будет доступен при вызове из вектора.
  2. Вам необходимо использовать много памяти, которая может переполнить стек. Здорово, если с такой проблемой не приходится сталкиваться (а с ней сталкиваются очень редко), потому что это «вне компетенции» C++, но к сожалению, иногда приходится решать и эту задачу.
  3. Вы, например, точно не знаете размер массива, который придется использовать. Как известно, в C++ массивы при определении имеют фиксированный размер. Это может вызвать проблемы, например, при считывании пользовательского ввода. Указатель же определяет только тот участок в памяти, куда будет записано начало массива, грубо говоря, не ограничивая его размер.

Если использование динамического распределения необходимо, то вам стоит инкапсулировать его с помощью умного указателя или другого поддерживающего идиому «Получение ресурса есть инициализация» типа (стандартные контейнеры ее поддерживают — это идиома, в соответствии с которой ресурс: блок памяти, файл, сетевое соединение и т. п. — при получении инициализируется в конструкторе, а затем аккуратно уничтожается деструктором). Умными являются, например, указатели std::unique_ptr и std::shared_ptr.

Указатели

Однако есть случаи, когда использование указателей оправдано не только с точки зрения динамического распределения памяти, но почти всегда есть альтернативный путь, без использования указателей, который вам и следует выбрать. Как и ранее, скажем: всегда делайте выбор в пользу альтернативы, если нет особенной необходимости в использовании указателей.

Случаями, когда использование указателей можно рассматривать как возможный вариант, можно назвать следующие:

  1. Ссылочная семантика. Иногда может быть необходимо обратиться к объекту (вне зависимости от того, как под него распределена память), поскольку вы хотите обратиться в функции именно в этому объекту, а не его копии — т. е. когда вам требуется реализовать передачу по ссылке. Однако в большинстве случаев, здесь достаточно использовать именно ссылку, а не указатель, потому что именно для этого ссылки и созданы. Заметьте, что это несколько разные вещи с тем, что описано в пункте 1 выше. Но если вы можете обратиться к копии объекта, то и ссылку использовать нет необходимости (но заметьте, копирование объекта — дорогая операция).
  2. Полиморфизм. Вызов функций в рамках полиморфизма (динамический класс объекта) возможен с помощью ссылки или указателя. И снова, использование ссылок более предпочтительно.
  3. Необязательный объект. В этом случае можно использовать nullptr, чтобы указать, что объект опущен. Если это аргумент функции, то лучше сделайте реализацию с аргументами по умолчанию или перегрузкой. С другой стороны, можно использовать тип, который инкапсулирует такое поведение, например boost::optional (измененный в C++14 std::optional).
  4. Повышение скорости компиляции. Вам может быть необходимо разделить единицы компиляции (compilation units). Одним из эффективных применений указателей является предварительная декларация (т. к. для использования объекта, вам необходимо предварительно его определить). Это позволить вам разнести единицы компиляции, что может положительно сказаться на ускорении времени компиляции, внушительно уменьшив время, затрачиваемое на этот процесс.
  5. Взаимодействие с библиотекой C или C-подобной. Здесь вам придется использовать сырые указатели, освобождение памяти из под которых вы производите в самый последний момент. Получить сырой указатель можно из умного указателя, например, операцией get. Если библиотека использует память, которая в последствии должна быть освобождена вручную, вы можете оформить деструктор в умном указателе.

Источник: stackoverflow.com
Перевод: vk.com

Изучение С++

16 января 2014, 19:13

Начал изучать С++, книжку по Си я когда-то не дочитал.
Страутструп сложный, прочитал 20% и дальше стало непонятно, для для более легкого изучения лучше начать с Липпман и др. Язык программирования С++, на 2014 год последнее издание — пятое.