Почему я должен использовать указатель, а не сам объект?
Я исхожу из фона Java и начал работать с объектами на С++. Но мне пришло в голову то, что люди часто используют указатели на объекты, а не сами объекты, например это объявление:
Object *myObject = new Object;
а не:
Object myObject;
Или вместо использования функции, скажем testFunc()
, вот так:
myObject.testFunc();
мы должны написать:
myObject->testFunc();
Но я не могу понять, почему мы должны это делать. Я бы предположил, что это связано с эффективностью и скоростью, поскольку мы получаем прямой доступ к адресу памяти. Я прав?
Ответы
Ответ 1
Очень жаль, что вы часто видите динамическое распределение. Это просто показывает, сколько есть плохих программистов C++.
В некотором смысле, у вас есть два вопроса, объединенные в один. Во-первых, когда мы должны использовать динамическое размещение (используя new
)? Во-вторых, когда мы должны использовать указатели?
Важное сообщение о том, что вы всегда должны использовать соответствующий инструмент для работы. Почти во всех ситуациях есть что-то более подходящее и более безопасное, чем выполнение динамического выделения вручную и/или использование необработанных указателей.
Динамическое распределение
В своем вопросе вы продемонстрировали два способа создания объекта. Основным отличием является срок хранения объекта. При выполнении Object myObject;
внутри блока объект создается с автоматическим сроком хранения, что означает, что он будет автоматически уничтожен при выходе из области видимости. Когда вы делаете new Object()
, объект имеет динамическую продолжительность хранения, что означает, что он остается в живых, пока вы явно не delete
его. Вы должны использовать динамическую продолжительность хранения только тогда, когда вам это нужно. То есть вы всегда должны предпочитать создавать объекты с автоматической продолжительностью хранения, когда можете.
Основные две ситуации, в которых вам может потребоваться динамическое размещение:
- Вам нужен объект, чтобы пережить текущую область - этот конкретный объект в этой конкретной ячейке памяти, а не его копия. Если у вас все в порядке с копированием/перемещением объекта (большую часть времени вам следует), вы должны предпочесть автоматический объект.
- Вам нужно выделить много памяти, которая может легко заполнить стек. Было бы неплохо, если бы нам не пришлось беспокоиться об этом (большую часть времени вам не нужно было бы), поскольку это действительно выходит за рамки C++, но, к сожалению, нам приходится иметь дело с реальностью системы, для которых мы разрабатываем.
Когда вам абсолютно необходимо динамическое распределение, вы должны инкапсулировать его в умный указатель или какой-то другой тип, который выполняет RAII (например, стандартные контейнеры). Умные указатели обеспечивают семантику владения динамически размещенными объектами. Взгляните, например, на std::unique_ptr
и std::shared_ptr
. Если вы используете их надлежащим образом, вы можете почти полностью избежать выполнения своего собственного управления памятью (см. Правило нуля).
указатели
Тем не менее, есть и другие, более общие способы использования необработанных указателей, помимо динамического выделения, но у большинства есть альтернативы, которые вы должны предпочесть. Как и прежде, всегда предпочитайте альтернативы, если вам не нужны указатели.
-
Вам нужна ссылочная семантика. Иногда вы хотите передать объект с помощью указателя (независимо от того, как он был выделен), потому что вы хотите, чтобы функция, которой вы его передаете, имела доступ к этому конкретному объекту (а не к его копии). Однако в большинстве ситуаций вы должны предпочитать ссылочные типы указателям, потому что это именно то, для чего они предназначены. Обратите внимание, что это не обязательно означает продление срока службы объекта за пределы текущей области, как в ситуации 1 выше. Как и раньше, если вы можете передавать копию объекта, вам не нужна семантика ссылок.
-
Вам нужен полиморфизм. Вы можете вызывать функции только полиморфно (то есть в соответствии с динамическим типом объекта) через указатель или ссылку на объект. Если это поведение вам нужно, то вам нужно использовать указатели или ссылки. Опять же, ссылки должны быть предпочтительными.
-
Вы хотите представить, что объект является необязательным, позволяя передавать nullptr
, когда объект опущен. Если это аргумент, вы должны использовать аргументы по умолчанию или перегрузки функций. В противном случае вам следует предпочтительно использовать тип, который инкапсулирует это поведение, например std::optional
option (введенный в C++ 17 - с более ранними стандартами C++, используйте boost::optional
option).
-
Вы хотите отделить единицы компиляции, чтобы улучшить время компиляции. Полезное свойство указателя заключается в том, что вам требуется только предварительное объявление указательного типа (для фактического использования объекта вам понадобится определение). Это позволяет вам отделить части вашего процесса компиляции, что может значительно сократить время компиляции. Смотрите идиома Пимпл.
-
Вам необходимо взаимодействовать с библиотекой C или библиотекой в стиле C. На этом этапе вы вынуждены использовать сырые указатели. Лучшее, что вы можете сделать, это убедиться, что ваши сырые указатели теряются только в самый последний момент. Вы можете получить необработанный указатель из умного указателя, например, используя его функцию-член get
. Если библиотека выполняет для вас какое-то выделение, которое она ожидает от вас освободить с помощью дескриптора, вы часто можете заключить этот дескриптор в интеллектуальный указатель с помощью специального средства удаления, которое будет соответствующим образом освобождать объект.
Ответ 2
Существует много вариантов использования указателей.
Полиморфное поведение. Для полиморфных типов указатели (или ссылки) используются для исключения нарезки:
class Base { ... };
class Derived : public Base { ... };
void fun(Base b) { ... }
void gun(Base* b) { ... }
void hun(Base& b) { ... }
Derived d;
fun(d); // oops, all Derived parts silently "sliced" off
gun(&d); // OK, a Derived object IS-A Base object
hun(d); // also OK, reference also doesn't slice
Ссылочная семантика и избежание копирования. Для неполиморфных типов указатель (или ссылка) позволит избежать копирования потенциально дорогого объекта
Base b;
fun(b); // copies b, potentially expensive
gun(&b); // takes a pointer to b, no copying
hun(b); // regular syntax, behaves as a pointer
Обратите внимание, что С++ 11 имеет семантику перемещения, которая может избежать многих копий дорогостоящих объектов в аргумент функции и в качестве возвращаемых значений. Но использование указателя, безусловно, позволит избежать этого и позволит использовать несколько указателей на одном и том же объекте (тогда как объект может перемещаться только один раз).
Приобретение ресурсов. Создание указателя на ресурс с помощью оператора new
- это анти-шаблон в современном С++. Используйте специальный класс ресурсов (один из стандартных контейнеров) или умный указатель (std::unique_ptr<>
или std::shared_ptr<>
). Рассмотрим:
{
auto b = new Base;
... // oops, if an exception is thrown, destructor not called!
delete b;
}
против.
{
auto b = std::make_unique<Base>();
... // OK, now exception safe
}
Необработанный указатель должен использоваться только как "представление" и никоим образом не связан с владением, будь то прямое создание или неявно через возвращаемые значения. См. Также этот Q & A из часто задаваемых вопросов С++.
Более мелкозернистый контроль времени жизни Каждый раз, когда копируется общий указатель (например, как аргумент функции), ресурс, на который он указывает, сохраняется в живых. Регулярные объекты (не созданные new
, либо непосредственно вами, либо внутри класса ресурсов) уничтожаются при выходе из области видимости.
Ответ 3
Есть много отличных ответов на этот вопрос, в том числе важные варианты использования форвардных деклараций, полиморфизм и т.д., но я чувствую, что часть "души" вашего вопроса не отвечает - а именно, что означают разные синтаксисы для Java и С++.
Давайте рассмотрим ситуацию, сравнивающую два языка:
Java:
Object object1 = new Object(); //A new object is allocated by Java
Object object2 = new Object(); //Another new object is allocated by Java
object1 = object2;
//object1 now points to the object originally allocated for object2
//The object originally allocated for object1 is now "dead" - nothing points to it, so it
//will be reclaimed by the Garbage Collector.
//If either object1 or object2 is changed, the change will be reflected to the other
Ближайшим эквивалентом этого является:
С++:
Object * object1 = new Object(); //A new object is allocated on the heap
Object * object2 = new Object(); //Another new object is allocated on the heap
delete object1;
//Since C++ does not have a garbage collector, if we don't do that, the next line would
//cause a "memory leak", i.e. a piece of claimed memory that the app cannot use
//and that we have no way to reclaim...
object1 = object2; //Same as Java, object1 points to object2.
Посмотрите альтернативный способ С++:
Object object1; //A new object is allocated on the STACK
Object object2; //Another new object is allocated on the STACK
object1 = object2;//!!!! This is different! The CONTENTS of object2 are COPIED onto object1,
//using the "copy assignment operator", the definition of operator =.
//But, the two objects are still different. Change one, the other remains unchanged.
//Also, the objects get automatically destroyed once the function returns...
Лучший способ подумать о том, что - более или менее - Java (неявно) обрабатывает указатели на объекты, тогда как С++ может обрабатывать либо указатели на объекты, либо сами объекты.
Есть исключения из этого - например, если вы объявляете "примитивные" типы Java, это фактические значения, которые копируются, а не указатели.
Таким образом,
Java:
int object1; //An integer is allocated on the stack.
int object2; //Another integer is allocated on the stack.
object1 = object2; //The value of object2 is copied to object1.
Тем не менее, использование указателей НЕ обязательно является правильным или неправильным способом обработки вещей; однако другие ответы покрывали это удовлетворительно. Общая идея заключается в том, что в С++ у вас гораздо больше контроля над временем жизни объектов и от того, где они будут жить.
Возьмите главную точку - конструкция Object * object = new Object()
на самом деле является тем, что ближе всего к семантике Java (или С#).
Ответ 4
Еще одна веская причина использовать указатели для форвардных объявлений. В достаточно большом проекте они могут ускорить время компиляции.
Ответ 5
Предисловие
Java не похож на C++, вопреки обману. Java hype machine хотел бы, чтобы вы поверили, что, поскольку Java имеет C++, как синтаксис, языки похожи. Ничто не может быть дальше от истины. Эта дезинформация является частью причины, по которой Java-программисты переходят на C++ и используют синтаксис, подобный Java, без понимания последствий их кода.
Вперед мы идем
Но я не могу понять, почему мы должны это делать. Я бы предположил, что это связано с эффективностью и скоростью, поскольку мы получаем прямой доступ к адресу памяти. Я прав?
Напротив, на самом деле. Куча намного медленнее, чем стек, потому что стек очень прост по сравнению с кучей. Автоматические переменные хранилища (ака стековые переменные) вызывают их деструкторы, когда они выходят за рамки. Например:
{
std::string s;
}
// s is destroyed here
С другой стороны, если вы используете динамически распределенный указатель, его деструктор должен вызываться вручную. delete
вызывает этот деструктор для вас.
{
std::string* s = new std::string;
}
delete s; // destructor called
Это не имеет ничего общего с new
синтаксисом, распространенным в С# и Java. Они используются для совершенно разных целей.
Преимущества динамического распределения
1. Вам не нужно заранее знать размер массива
Одна из первых проблем, с которыми сталкиваются многие программисты C++, заключается в том, что, когда они принимают произвольный вход от пользователей, вы можете выделить фиксированный размер для переменной стека. Вы не можете изменить размер массивов. Например:
char buffer[100];
std::cin >> buffer;
// bad input = buffer overflow
Конечно, если вы использовали std::string
вместо этого, std::string
внутренне меняет размеры, поэтому это не должно быть проблемой. Но по существу решение этой проблемы - динамическое распределение. Вы можете выделить динамическую память на основе ввода пользователя, например:
int * pointer;
std::cout << "How many items do you need?";
std::cin >> n;
pointer = new int[n];
Сторона примечания: Одна ошибка, которую делают многие новички, - использование массивов переменной длины. Это расширение GNU, а также одно в Clang, потому что они отражают многие расширения GCC. Поэтому нельзя полагаться на следующий int arr[n]
.
Поскольку куча намного больше, чем стек, можно произвольно распределить/перераспределить столько памяти, сколько ему нужно, тогда как у стека есть ограничение.
2. Массивы не являются указателями
Как вы это посоветовали? Ответ станет ясным, как только вы поймете путаницу/миф за массивами и указателями. Обычно считается, что они одинаковы, но это не так. Этот миф исходит из того факта, что указатели могут быть индексированы так же, как массивы, и из-за разложения массивов на указатели на верхнем уровне в объявлении функции. Однако, как только массив распадается на указатель, указатель теряет свой sizeof
информации. Таким образом, sizeof(pointer)
даст размер указателя в байтах, который обычно составляет 8 байтов в 64-битной системе.
Вы не можете назначать массивы, только инициализировать их. Например:
int arr[5] = {1, 2, 3, 4, 5}; // initialization
int arr[] = {1, 2, 3, 4, 5}; // The standard dictates that the size of the array
// be given by the amount of members in the initializer
arr = { 1, 2, 3, 4, 5 }; // ERROR
С другой стороны, вы можете делать все, что хотите, с помощью указателей. К сожалению, поскольку различие между указателями и массивами размахивается руками на Java и С#, новички не понимают разницы.
3. Полиморфизм
Java и С# имеют средства, позволяющие рассматривать объекты как другие, например, используя ключевое слово as
. Поэтому, если кто-то хотел рассматривать объект Entity
как объект Player
, можно было бы использовать Player player = Entity as Player;
Это очень полезно, если вы намерены вызывать функции на однородном контейнере, которые должны применяться только к определенному типу. Функциональность может быть достигнута следующим образом:
std::vector<Base*> vector;
vector.push_back(&square);
vector.push_back(&triangle);
for (auto& e : vector)
{
auto test = dynamic_cast<Triangle*>(e); // I only care about triangles
if (!test) // not a triangle
e.GenericFunction();
else
e.TriangleOnlyMagic();
}
Скажем, если бы только треугольники имели функцию Rotate, это была бы ошибка компилятора, если бы вы попытались вызвать ее на всех объектах класса. Используя dynamic_cast
, вы можете имитировать ключевое слово as
. Чтобы быть ясным, если сбой выполняется, он возвращает недопустимый указатель. Таким образом, !test
по существу является сокращением для проверки того, является ли test
NULL или недопустимым указателем, что означает, что приведение не выполнено.
Преимущества автоматических переменных
Увидев все великие вещи, которые может сделать динамическое распределение, вы, вероятно, задаетесь вопросом, почему никто не будет использовать динамическое распределение все время? Я уже сказал вам одну причину: куча медленная. И если вам не нужна вся эта память, вы не должны злоупотреблять ею. Итак, вот некоторые недостатки в каком-то конкретном порядке:
-
Он подвержен ошибкам. Распределение памяти вручную опасно, и вы склонны к утечкам. Если вы не умеете использовать отладчик или valgrind
(средство утечки памяти), вы можете вытащить свои волосы из головы. К счастью, идиомы RAII и умные указатели немного облегчают это, но вы должны быть знакомы с такими практиками, как "Правило из трех" и "Правило пяти". Вниманию много информации, и новички, которые либо не знают, либо не заботятся, попадают в эту ловушку.
-
Это не обязательно. В отличие от Java и С#, где идиоматично использовать new
ключевое слово во всем мире, в C++ вы должны использовать его, если вам нужно. Обычная фраза идет, все выглядит как гвоздь, если у вас есть молоток. В то время как начинающие, начинающие с C++, боятся указателей и учатся использовать переменные стека по привычке, программисты Java и С# начинают с использования указателей, не понимая этого! Это буквально отступает на неправильную ногу. Вы должны отказаться от всего, что знаете, потому что синтаксис - это одно, а изучение языка - другое.
1. (N) RVO - Aka, (Именованный) Оптимизация возвращаемого значения
Одна оптимизация, которую делают многие компиляторы, - это вещи, называемые оптимизацией элиции и возвращаемого значения. Эти вещи могут избежать ненужных копий, которые полезны для очень больших объектов, таких как вектор, содержащий много элементов. Обычно обычной практикой является использование указателей для передачи права собственности, а не копирования больших объектов для их перемещения. Это привело к возникновению семантики перемещения и интеллектуальных указателей.
Если вы используете указатели, (N) RVO НЕ происходит. Более выгодно и менее подвержено ошибкам использовать (N) RVO, а не возвращать или пропускать указатели, если вас беспокоит оптимизация. Ошибка утечки может произойти, если вызывающая функция отвечает за delete
динамически выделенного объекта и т.д. Трудно отследить право собственности на объект, если указатели передаются как горячий картофель. Просто используйте переменные стека, потому что это проще и лучше.
Ответ 6
С++ дает вам три способа передать объект: по указателю, по ссылке и по значению. Java ограничивает вас последним (единственное исключение - это примитивные типы, такие как int, boolean и т.д.). Если вы хотите использовать С++ не просто как странную игрушку, тогда вам лучше узнать разницу между этими тремя способами.
Java притворяется, что нет такой проблемы, как "кто и когда должен ее уничтожить?". Ответ: сборщик мусора, большой и ужасный. Тем не менее, он не может обеспечить 100% защиту от утечек памяти (да, java может утечка памяти). На самом деле, GC дает вам ложное чувство безопасности. Чем больше ваш внедорожник, тем длиннее ваш путь к эвакуатору.
С++ оставляет вас лицом к лицу с управлением жизненным циклом объекта. Ну, есть способы справиться с этим (умные указатели, QObject в Qt и т.д.), Но ни один из них не может использоваться в ' огонь и забыть ", как GC: вы должны всегда иметь в виду обработку памяти. Вы не только должны заботиться об уничтожении объекта, но и избегать уничтожения одного и того же объекта более одного раза.
Не испугался? Хорошо: циклические ссылки - обрабатывайте их самостоятельно, человек. И помните: убивайте каждый объект ровно один раз, мы С++ runtimes не любим тех, кто возится с трупами, оставляют мертвых только.
Итак, вернемся к вашему вопросу.
Когда вы передаете свой объект по значению, а не по указателю или по ссылке, вы копируете объект (весь объект, будь то пара байтов или огромный дамп базы данных), вы достаточно умны, чтобы избежать последнего, не так ли?) каждый раз, когда вы делаете "=". И чтобы получить доступ к членам объекта, вы используете '.' (Точка).
Когда вы передаете свой объект по указателю, вы копируете только несколько байтов (4 на 32-битных системах, 8 на 64-битных), а именно - адрес этого объекта. И чтобы показать это всем, вы используете этот причудливый оператор "- > " при доступе к членам. Или вы можете использовать комбинацию "*" и ".".
Когда вы используете ссылки, вы получаете указатель, который претендует на значение. Это указатель, но вы получаете доступ к элементам через ".".
И, чтобы взорвать свой ум еще раз: когда вы объявляете несколько переменных, разделенных запятыми, тогда (смотрите на руки):
- Тип предоставляется всем
- Значение/модификатор указателя/ссылки является индивидуальным
Пример:
struct MyStruct
{
int* someIntPointer, someInt; //here comes the surprise
MyStruct *somePointer;
MyStruct &someReference;
};
MyStruct s1; //we allocated an object on stack, not in heap
s1.someInt = 1; //someInt is of type 'int', not 'int*' - value/pointer modifier is individual
s1.someIntPointer = &s1.someInt;
*s1.someIntPointer = 2; //now s1.someInt has value '2'
s1.somePointer = &s1;
s1.someReference = s1; //note there is no '&' operator: reference tries to look like value
s1.somePointer->someInt = 3; //now s1.someInt has value '3'
*(s1.somePointer).someInt = 3; //same as above line
*s1.somePointer->someIntPointer = 4; //now s1.someInt has value '4'
s1.someReference.someInt = 5; //now s1.someInt has value '5'
//although someReference is not value, it members are accessed through '.'
MyStruct s2 = s1; //'NO WAY' the compiler will say. Go define your '=' operator and come back.
//OK, assume we have '=' defined in MyStruct
s2.someInt = 0; //s2.someInt == 0, but s1.someInt is still 5 - it two completely different objects, not the references to the same one
Ответ 7
В С++ объекты, выделенные в стеке (с использованием инструкции Object object;
внутри блока), будут жить только в пределах области, в которой они объявлены. Когда блок кода завершает выполнение, объявленный объект уничтожается.
Если вы выделяете память в кучу, используя Object* obj = new Object()
, они продолжают жить в куче, пока не назовете delete obj
.
Я бы создал объект в куче, когда мне нравится использовать этот объект не только в блоке кода, который его объявил/выделил.
Ответ 8
Но я не могу понять, почему мы должны использовать его так?
Я буду сравнивать, как он работает внутри тела функции, если вы используете:
Object myObject;
Внутри функции ваш myObject
будет уничтожен после возвращения этой функции. Поэтому это полезно, если вам не нужен ваш объект вне вашей функции. Этот объект будет помещен в текущий стек потока.
Если вы пишете внутри тела функции:
Object *myObject = new Object;
то экземпляр класса Object, указанный myObject
, не будет уничтожен после завершения функции, а выделение находится в куче.
Теперь, если вы программист Java, то второй пример ближе к тому, как распределение объектов работает под java. Эта строка: Object *myObject = new Object;
эквивалентно java: Object myObject = new Object();
, Разница в том, что в java myObject будет получен мусор, а в c++ он не будет освобожден, вы должны где-то явно называть 'delete myObject;' в противном случае вы будете вводить утечки памяти.
Начиная с c++ 11 вы можете использовать безопасные способы динамических распределений: new Object
, сохраняя значения в shared_ptr/unique_ptr.
std::shared_ptr<std::string> safe_str = make_shared<std::string>("make_shared");
// since c++14
std::unique_ptr<std::string> safe_str = make_unique<std::string>("make_shared");
Кроме того, объекты очень часто хранятся в контейнерах, таких как map-s или vector-s, они автоматически управляют временем жизни ваших объектов.
Ответ 9
Технически это проблема распределения памяти, однако здесь есть еще два практических аспекта.
Это связано с двумя вещами:
1) Область действия, когда вы определяете объект без указателя, вы больше не сможете получить к нему доступ после того, как он определен в блоке кода, тогда как если вы определяете указатель с "новым", вы можете получить к нему доступ из любой точки указатель на эту память, пока вы не назовете "удалить" на том же указателе.
2) Если вы хотите передать аргументы функции, которую вы хотите передать указателю или ссылке, чтобы быть более эффективными. Когда вы передаете объект Object, объект копируется, если это объект, который использует большую часть памяти, это может быть потребляемое ЦП (например, вы копируете вектор, полный данных). Когда вы передаете указатель, все, что вы передаете, это один int (в зависимости от реализации, но большинство из них - один int).
Кроме того, вам нужно понять, что "новый" выделяет память в куче, которая должна быть освобождена в какой-то момент. Когда вам не нужно использовать "новый", я предлагаю вам использовать обычное определение объекта "в стеке".
Ответ 10
Ну, главный вопрос: почему я должен использовать указатель, а не сам объект? И мой ответ: вы должны (почти) никогда не использовать указатель вместо объекта, потому что C++ имеет ссылки, он более безопасен, чем указатели и гарантирует ту же производительность, что и указатели.
Еще одна вещь, которую вы упомянули в своем вопросе:
Object *myObject = new Object;
Как это работает? Он создает указатель типа Object
, выделяет память для соответствия одному объекту и вызывает конструктор по умолчанию, звучит хорошо, правильно? Но на самом деле это не так хорошо, если вы динамически выделяете память (используется ключевое слово new
), вам также необходимо освободить память вручную, это означает, что код должен иметь:
delete myObject;
Это вызывает деструктор и освобождает память, выглядит легко, однако в больших проектах может быть сложно обнаружить, если один поток освободил память или нет, но для этой цели вы можете попробовать общие указатели, это немного снижает производительность, но с гораздо проще работать с их.
И теперь какое-то введение завершено и возвращается к вопросу.
Вы можете использовать указатели вместо объектов для повышения производительности при передаче данных между функциями.
Взгляните, у вас есть std::string
(это тоже объект), и он содержит очень много данных, например большой XML, теперь вам нужно его разобрать, но для этого у вас есть функция void foo(...)
которая может декларироваться по-разному:
-
void foo(std::string xml);
В этом случае вы скопируете все данные из вашей переменной в стек функций, потребуется некоторое время, поэтому ваша производительность будет низкой. -
void foo(std::string* xml);
В этом случае вы передадите указатель на объект с той же скоростью, что и передавая переменную size_t
, однако это объявление имеет склонность к ошибкам, потому что вы можете передать указатель NULL
или неверный указатель. Указатели обычно используются в C
потому что у него нет ссылок. -
void foo(std::string& xml);
Здесь вы передаете ссылку, в основном это то же самое, что и передающий указатель, но компилятор делает некоторые вещи, и вы не можете передать недопустимую ссылку (на самом деле можно создать ситуацию с недопустимой ссылкой, но это компилятор tricking). -
void foo(const std::string* xml);
Здесь то же самое, что и второе, значение указателя не может быть изменено. -
void foo(const std::string& xml);
Здесь то же самое, что и третье, но значение объекта не может быть изменено.
Что еще я хочу упомянуть, вы можете использовать эти 5 способов передачи данных независимо от того, какой путь выделения вы выбрали (с new
или регулярным).
Другое дело, когда вы создаете объект обычным способом, вы выделяете память в стеке, но пока вы создаете его с new
вы выделяете кучу. Гораздо быстрее выделять стек, но он очень мал для действительно больших массивов данных, поэтому, если вам нужен большой объект, вы должны использовать кучу, потому что вы можете получить переполнение стека, но обычно эта проблема решается с использованием контейнеров STL и запоминает std::string
также является контейнером, некоторые ребята забыли его :)
Ответ 11
Скажем, что у вас есть class A
, которые содержат class B
Если вы хотите вызвать некоторую функцию class B
вне class A
, вы просто получите указатель на этот класс, и вы сможете делать все, что хотите, и оно будет также измените контекст class B
в class A
Но будьте осторожны с динамическим объектом
Ответ 12
Существует много преимуществ использования указателей для объекта -
- Эффективность (как вы уже указывали). Передача объектов в
функции означают создание новых копий объекта.
- Работа с объектами из сторонних библиотек. Если ваш объект
принадлежит стороннему коду, и авторы намереваются использовать свои объекты только с помощью указателей (без конструкторов копирования и т.д.), единственный способ, которым вы можете обойти это
объект использует указатели. Передача по значению может вызвать проблемы. (Deep
копия/мелкая копия).
- если объект владеет ресурсом, и вы хотите, чтобы собственность не была защищена другими объектами.
Ответ 13
Это обсуждается подробно, но в Java все это указатель. Он не делает различий между распределением стека и кучи (все объекты выделены в куче), поэтому вы не понимаете, что используете указатели. В С++ вы можете смешивать два, в зависимости от ваших требований к памяти. Производительность и использование памяти более детерминированы в С++ (duh).
Ответ 14
Object *myObject = new Object;
Выполнение этого приведет к созданию ссылки на объект (в куче), который должен быть удален явно, чтобы избежать утечки памяти.
Object myObject;
Это создаст объект (myObject) автоматического типа (в стеке), который будет автоматически удален, когда объект (myObject) выходит из области видимости.
Ответ 15
Предположим, вы хотите посетить своих друзей дома,
Скажи ему, чтобы он привез его домой к тебе. Это сам передающий объект.
Затем спросите его его домашний адрес, он даст вам адрес. Теперь вы можете посетить его дом, это Pass by Pointer.
Получение адреса от вашего друга проще, чем копирование его дома повсюду.
вот почему вы должны использовать указатели на объекты.
Ответ 16
Указатель непосредственно ссылается на расположение памяти объекта. Java не имеет ничего подобного. Java имеет ссылки, которые ссылаются на местоположение объекта через хеш-таблицы. Вы не можете делать что-либо вроде арифметики указателей в Java с этими ссылками.
Чтобы ответить на ваш вопрос, это просто ваше предпочтение. Я предпочитаю использовать синтаксис типа Java.
Ответ 17
Вы не должны. Люди (многие, к сожалению) пишут это из-за невежества.
Иногда динамическое распределение имеет свое место, но в приведенных вами примерах это неверно.
Если вы хотите думать об эффективности, то это хуже, потому что оно вводит косвенность без уважительной причины. Такой вид программирования медленнее и подвержен ошибкам.
Ответ 18
С указателями,
-
может напрямую разговаривать с памятью.
-
может предотвратить множество утечек памяти в программе, манипулируя указателями.
Ответ 19
Одной из причин использования указателей является взаимодействие с функциями C. Другая причина - сохранить память; например: вместо передачи объекта, который содержит много данных, и имеет процессор-интенсивный экземпляр-конструктор для функции, просто передайте указатель на объект, экономя память и скорость, особенно если вы находитесь в цикле, однако ссылка будет лучше в этом случае, если вы не используете массив в стиле C.
Ответ 20
В областях, где использование памяти имеет свою премию, указатели удобны. Например, рассмотрим минимаксный алгоритм, в котором тысячи узлов будут генерироваться с использованием рекурсивной подпрограммы, а затем использовать их для оценки следующего наилучшего движения в игре, способности освобождения или сброса (как в интеллектуальных указателях) значительно уменьшают потребление памяти. В то время как переменная non-pointer продолжает занимать пространство до тех пор, пока рекурсивный вызов не вернет значение.
Ответ 21
Я буду включать один важный пример использования указателя. Когда вы храните некоторый объект в базовом классе, но он может быть полиморфным.
Class Base1 {
};
Class Derived1 : public Base1 {
};
Class Base2 {
Base *bObj;
virtual void createMemerObects() = 0;
};
Class Derived2 {
virtual void createMemerObects() {
bObj = new Derived1();
}
};
Таким образом, в этом случае вы не можете объявить bObj как прямой объект, у вас должен быть указатель.
Ответ 22
"Голь на выдумки хитра." Самое важное отличие, которое я хотел бы отметить, - это результат моего собственного опыта кодирования. Иногда вам нужно передать объекты в функции. В этом случае, если ваш объект имеет очень большой класс, то передача его в виде объекта будет скопировать его состояние (которое вам может не понадобиться..AND CAN BIG OVERHEAD), что приводит к накладным расходам на копирование объекта. 4-байтовый размер (предполагается 32 бит). Другие причины уже упомянуты выше...
Ответ 23
Уже есть много отличных ответов, но позвольте мне привести один пример:
У меня есть простой класс Item:
class Item
{
public:
std::string name;
int weight;
int price;
};
Я делаю вектор, чтобы содержать кучу из них.
std::vector<Item> inventory;
Я создаю миллион объектов предметов и нажимаю их на вектор. Я сортирую вектор по имени, а затем выполняю простой итеративный двоичный поиск определенного имени элемента. Я тестирую программу, и для завершения выполнения требуется более 8 минут. Затем я меняю свой инвентарь так:
std::vector<Item *> inventory;
... и создайте мои объекты Item Item через new. ТОЛЬКО изменения, которые я делаю для моего кода, - это использовать указатели на Элементы, за исключением цикла, который я добавляю для очистки памяти в конце. Эта программа работает менее чем за 40 секунд или лучше, чем 10-кратное увеличение скорости.
EDIT: код находится в http://pastebin.com/DK24SPeW
При оптимизации компилятора он показывает только увеличение на 3,4 раза на машине, на которой я только что протестировал ее, что по-прежнему значительно.