0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>Как правило, многие программисты понимают разницу между объектами и указателями на объекты. Тем не менее не всегда ясно, в пользу какого из вышеперечисленных способов обращения делать выбор. Попробуем ответить на этот вопрос.</p>
1
<p>Как правило, многие программисты понимают разницу между объектами и указателями на объекты. Тем не менее не всегда ясно, в пользу какого из вышеперечисленных способов обращения делать выбор. Попробуем ответить на этот вопрос.</p>
2
<p>Однажды на<a>StackOverflow</a>прозвучал вопрос следующего содержания: "Why should I use a pointer rather than the object itself?" - "Почему я должен использовать указатель, а не сам объект?"</p>
2
<p>Однажды на<a>StackOverflow</a>прозвучал вопрос следующего содержания: "Why should I use a pointer rather than the object itself?" - "Почему я должен использовать указатель, а не сам объект?"</p>
3
<p>И действительно, некоторые программисты<strong>применяют указатели на объекты чаще, чем сами объекты</strong>, используя такую конструкцию:</p>
3
<p>И действительно, некоторые программисты<strong>применяют указатели на объекты чаще, чем сами объекты</strong>, используя такую конструкцию:</p>
4
Object *myObject=new Object;<p>а не такую:</p>
4
Object *myObject=new Object;<p>а не такую:</p>
5
<p>Также и с методами, почему вместо этого:</p>
5
<p>Также и с методами, почему вместо этого:</p>
6
<p>следует писать это:</p>
6
<p>следует писать это:</p>
7
<p>Разработчик, который задал вопрос, предположил, что это даёт выигрыш в скорости, ведь мы обращаемся напрямую к памяти. К слову, он перешёл в C++-программирование с Java. Что же, посмотрим, что ему ответили.</p>
7
<p>Разработчик, который задал вопрос, предположил, что это даёт выигрыш в скорости, ведь мы обращаемся напрямую к памяти. К слову, он перешёл в C++-программирование с Java. Что же, посмотрим, что ему ответили.</p>
8
<h2>Ответ</h2>
8
<h2>Ответ</h2>
9
<p>В языке программирования Java указатели в явном виде не используются, то есть разработчик не может обратиться в коде к объекту через указатель на него. Но на деле все типы в Java, за исключением базовых, являются ссылочными, ведь обращение к ним осуществляется по ссылке, хотя явно передать по ссылке параметр нельзя. Также следует заметить, что new в C++, в Java и C# - разные вещи.</p>
9
<p>В языке программирования Java указатели в явном виде не используются, то есть разработчик не может обратиться в коде к объекту через указатель на него. Но на деле все типы в Java, за исключением базовых, являются ссылочными, ведь обращение к ним осуществляется по ссылке, хотя явно передать по ссылке параметр нельзя. Также следует заметить, что new в C++, в Java и C# - разные вещи.</p>
10
<p>Чтобы понять, что же такое указатели в C++, рассмотрим два похожих фрагмента кода.<strong>Java</strong>:</p>
10
<p>Чтобы понять, что же такое указатели в C++, рассмотрим два похожих фрагмента кода.<strong>Java</strong>:</p>
11
Object object1 =newObject();// Новый объект Object object2 =newObject();// Очередной новый объект object1 = object2;// Обе переменные ссылаются на объект, на который ранее ссылалась object2 // При изменении объекта, на который ссылается object1, произойдет изменение и // object2, ведь это один и тот же объект<p>Эквивалентный код на<strong>C++</strong>:</p>
11
Object object1 =newObject();// Новый объект Object object2 =newObject();// Очередной новый объект object1 = object2;// Обе переменные ссылаются на объект, на который ранее ссылалась object2 // При изменении объекта, на который ссылается object1, произойдет изменение и // object2, ведь это один и тот же объект<p>Эквивалентный код на<strong>C++</strong>:</p>
12
Object* object1 =newObject();// Память выделена под новый объект // На эту память ссылается object1 Object* object2 =newObject();// Аналогично со вторым объектом delete object1; // В C++ нет системы сборки мусора, поэтому если этого не cделать, // к этой памяти программа уже не сможет получить доступ, // как минимум до перезапуска // Это не что иное, как утечка памяти object1 = object2;// Как и в Java, object1 указывает туда же, куда и object2<p>Но это - совсем другая вещь (<strong>C++</strong>):</p>
12
Object* object1 =newObject();// Память выделена под новый объект // На эту память ссылается object1 Object* object2 =newObject();// Аналогично со вторым объектом delete object1; // В C++ нет системы сборки мусора, поэтому если этого не cделать, // к этой памяти программа уже не сможет получить доступ, // как минимум до перезапуска // Это не что иное, как утечка памяти object1 = object2;// Как и в Java, object1 указывает туда же, куда и object2<p>Но это - совсем другая вещь (<strong>C++</strong>):</p>
13
Object object1;// Новый объект Object object2;// Еще один новый объект object1 = object2;// Полное копирование объекта object2 в object1, // а не переопределение указателя является очень дорогой операцией<p><strong>Но будет ли выигрыш в скорости, если мы будем обращаться к памяти напрямую?</strong></p>
13
Object object1;// Новый объект Object object2;// Еще один новый объект object1 = object2;// Полное копирование объекта object2 в object1, // а не переопределение указателя является очень дорогой операцией<p><strong>Но будет ли выигрыш в скорости, если мы будем обращаться к памяти напрямую?</strong></p>
14
<p>Оказывается, нет. Дело в том, что работа с указателями оформлена в виде кучи, а работа с объектами представляет собой стек - более простую и быструю структуру. Таким образом, в одном вопросе мы получили два: 1. Когда лучше использовать динамическое распределение памяти? 2. Когда лучше использовать указатели?</p>
14
<p>Оказывается, нет. Дело в том, что работа с указателями оформлена в виде кучи, а работа с объектами представляет собой стек - более простую и быструю структуру. Таким образом, в одном вопросе мы получили два: 1. Когда лучше использовать динамическое распределение памяти? 2. Когда лучше использовать указатели?</p>
15
<p>Конечно, всегда желательно выбирать для работы наиболее подходящий инструмент. Но почти всегда существует реализация лучше, чем с применением ручного динамического распределения (dynamicallocation) и/или "сырых" указателей.</p>
15
<p>Конечно, всегда желательно выбирать для работы наиболее подходящий инструмент. Но почти всегда существует реализация лучше, чем с применением ручного динамического распределения (dynamicallocation) и/или "сырых" указателей.</p>
16
<h2>Динамическое распределение</h2>
16
<h2>Динамическое распределение</h2>
17
<p>Формулировка вопроса подразумевает 2 способа создания объекта. Главное различие - срок их жизни (storageduration) в памяти программы.</p>
17
<p>Формулировка вопроса подразумевает 2 способа создания объекта. Главное различие - срок их жизни (storageduration) в памяти программы.</p>
18
<p>Применяя ObjectmyObject;, разработчик полагается на автоопределение срока жизни, то есть объект уничтожится сразу же после выхода из его области видимости. При этом Object *myObject = newObject; сохраняет жизнь объекту до того самого момента, пока разработчик вручную не удалит его из памяти с помощью команды delete. Применяйте последний вариант лишь тогда, когда это на самом деле надо. А значит, всегда выбирайте автоматическое определение срока хранения объекта, если, конечно, это возможно.</p>
18
<p>Применяя ObjectmyObject;, разработчик полагается на автоопределение срока жизни, то есть объект уничтожится сразу же после выхода из его области видимости. При этом Object *myObject = newObject; сохраняет жизнь объекту до того самого момента, пока разработчик вручную не удалит его из памяти с помощью команды delete. Применяйте последний вариант лишь тогда, когда это на самом деле надо. А значит, всегда выбирайте автоматическое определение срока хранения объекта, если, конечно, это возможно.</p>
19
<p>Что касается принудительного установления срока жизни, то оно используется в следующих случаях: • надо, чтобы объект существовал и после выхода из области его видимости. Мы говорим об именно этом объекте и именно в этой области памяти, а не про его копию. Если же для вас это не является принципиальным (а чаще всего это так), следует положиться на автоопределение срока жизни; • надо применять много памяти, которая может переполнить стек. В принципе, с этой проблемой сталкиваются редко, но, бывает, приходится решать и такую задачу; • точно не известен размер массива, который придётся использовать. Вы должны знать, что в C++ массивы имеют фиксированный размер при определении. Это иногда вызывает проблемы, к примеру, во время считывания пользовательского ввода. Указатель же определяет лишь тот участок в памяти, куда записывается начало массива.</p>
19
<p>Что касается принудительного установления срока жизни, то оно используется в следующих случаях: • надо, чтобы объект существовал и после выхода из области его видимости. Мы говорим об именно этом объекте и именно в этой области памяти, а не про его копию. Если же для вас это не является принципиальным (а чаще всего это так), следует положиться на автоопределение срока жизни; • надо применять много памяти, которая может переполнить стек. В принципе, с этой проблемой сталкиваются редко, но, бывает, приходится решать и такую задачу; • точно не известен размер массива, который придётся использовать. Вы должны знать, что в C++ массивы имеют фиксированный размер при определении. Это иногда вызывает проблемы, к примеру, во время считывания пользовательского ввода. Указатель же определяет лишь тот участок в памяти, куда записывается начало массива.</p>
20
<p>Когда применение динамического распределения необходимо, стоит инкапсулировать его посредством умного указателя (о таких указателях<a>мы уже писали</a>) или иного типа, поддерживающего концепцию “Получение ресурса есть инициализация”. Умными указателями, например, являются std::unique_ptr и std::shared_ptr.</p>
20
<p>Когда применение динамического распределения необходимо, стоит инкапсулировать его посредством умного указателя (о таких указателях<a>мы уже писали</a>) или иного типа, поддерживающего концепцию “Получение ресурса есть инициализация”. Умными указателями, например, являются std::unique_ptr и std::shared_ptr.</p>
21
<h2>Указатели</h2>
21
<h2>Указатели</h2>
22
<p>Иногда применение указателей оправдано не только из-за динамического распределения памяти. Однако помните, что практически всегда существует альтернативный путь, не предполагающий использование указателей - его и следует выбрать. То есть,<strong>если особая необходимость использовать указатели отсутствует, всегда выбирайте альтернативу</strong>.</p>
22
<p>Иногда применение указателей оправдано не только из-за динамического распределения памяти. Однако помните, что практически всегда существует альтернативный путь, не предполагающий использование указателей - его и следует выбрать. То есть,<strong>если особая необходимость использовать указатели отсутствует, всегда выбирайте альтернативу</strong>.</p>
23
<p>Однако рассмотрим случаи, когда применение указателей оправдано: •<strong>ссылочная семантика</strong>. Иногда нужно обратиться к объекту вне зависимости от того, каким образом под него распределена память, если вы желаете обратиться в функции именно к этому объекту, а не к его копии. То есть речь идёт о случае, когда надо реализовать передачу по ссылке. Но, опять же, чаще всего тут достаточно использовать не указатель, а именно ссылку, ведь как раз для этого и созданы ссылки. Но если есть возможность обратиться к копии объекта, то и ссылку, соответственно, использовать нет необходимости (однако учтите, что копирование объекта является дорогой операцией); •<strong>полиморфизм</strong>. С помощью ссылки либо указателя возможен вызов функций в рамках полиморфизма. Но и тут использование ссылок предпочтительнее; •<strong>необязательный объект</strong>. Тут можно применять<strong>nullptr</strong>, дабы указать, что объект опущен. Но если это аргумент функции, сделайте реализацию с аргументами по умолчанию либо перегрузкой. С другой стороны, вы можете использовать тип, инкапсулирующий такое поведение, допустим, boost::optional (измененный в C++14 std::optional); •<strong>повышение скорости компиляции</strong>. Иногда нужно разделить единицы компиляции (compilationunits). Один из эффективных вариантов использования указателей - предварительная декларация. В результате вы получите возможность разнести единицы компиляции, что обычно положительно сказывается на ускорении времени компиляции. •<strong>взаимодействие с С-подобной библиотекой</strong>. Здесь придётся задействовать сырые указатели, освобождение памяти из-под которых вы выполняете в самый последний момент. Сырой указатель можно получить из умного, используя операцию get. Если библиотека использует память, которая потом должна освобождаться вручную, можно оформить в умном указателе деструктор.</p>
23
<p>Однако рассмотрим случаи, когда применение указателей оправдано: •<strong>ссылочная семантика</strong>. Иногда нужно обратиться к объекту вне зависимости от того, каким образом под него распределена память, если вы желаете обратиться в функции именно к этому объекту, а не к его копии. То есть речь идёт о случае, когда надо реализовать передачу по ссылке. Но, опять же, чаще всего тут достаточно использовать не указатель, а именно ссылку, ведь как раз для этого и созданы ссылки. Но если есть возможность обратиться к копии объекта, то и ссылку, соответственно, использовать нет необходимости (однако учтите, что копирование объекта является дорогой операцией); •<strong>полиморфизм</strong>. С помощью ссылки либо указателя возможен вызов функций в рамках полиморфизма. Но и тут использование ссылок предпочтительнее; •<strong>необязательный объект</strong>. Тут можно применять<strong>nullptr</strong>, дабы указать, что объект опущен. Но если это аргумент функции, сделайте реализацию с аргументами по умолчанию либо перегрузкой. С другой стороны, вы можете использовать тип, инкапсулирующий такое поведение, допустим, boost::optional (измененный в C++14 std::optional); •<strong>повышение скорости компиляции</strong>. Иногда нужно разделить единицы компиляции (compilationunits). Один из эффективных вариантов использования указателей - предварительная декларация. В результате вы получите возможность разнести единицы компиляции, что обычно положительно сказывается на ускорении времени компиляции. •<strong>взаимодействие с С-подобной библиотекой</strong>. Здесь придётся задействовать сырые указатели, освобождение памяти из-под которых вы выполняете в самый последний момент. Сырой указатель можно получить из умного, используя операцию get. Если библиотека использует память, которая потом должна освобождаться вручную, можно оформить в умном указателе деструктор.</p>
24
<p>По материалам<a>StackOverflow</a>.</p>
24
<p>По материалам<a>StackOverflow</a>.</p>
25
25