19

Почему следует использовать указатель вместо самого объекта?

16

Я пришёл из мира Java и начал работать с объектами в C++. Но меня смутило, что многие разработчики часто используют указатели на объекты, а не сами объекты. Например, вот так:

Object *myObject = new Object;

вместо этого:

Object myObject;

Также, вместо того чтобы использовать функцию, скажем, testFunc(), как это:

myObject.testFunc();

нам нужно писать:

myObject->testFunc();

Я не могу понять, почему нужно использовать указатели. Я предполагал, что это связано с эффективностью и скоростью, ведь мы получаем прямой доступ к адресу в памяти. Прав ли я?

5 ответ(ов)

1

На этот вопрос уже дано множество отличных ответов, включая важные случаи использования предварительных деклараций, полиморфизма и т. д., но мне кажется, что часть "души" вашего вопроса не была затронута – а именно, что означают различные синтаксисы в Java и C++.

Давайте рассмотрим ситуацию, сравнивая эти два языка:

Java:

Object object1 = new Object(); // Создается новый объект
Object object2 = new Object(); // Создается еще один новый объект

object1 = object2; 
// object1 теперь указывает на объект, который изначально был выделен для object2
// Объект, который изначально был выделен для object1, теперь "мертв" – на него больше ничего не указывает,
// поэтому он будет освобожден сборщиком мусора.
// Если изменится либо object1, либо object2, изменения отразятся на другом объекте

Ближайший эквивалент этому в C++:

C++:

Object * object1 = new Object(); // Создается новый объект в куче
Object * object2 = new Object(); // Создается еще один новый объект в куче
delete object1; 
// Поскольку в C++ нет сборщика мусора, если мы этого не сделаем, следующая строка вызовет 
// "утечку памяти", то есть часть занятой памяти, которую приложение не сможет использовать 
// и которую мы не можем вернуть...

object1 = object2; // То же самое, что и в Java, object1 указывает на object2.

Посмотрим на альтернативный способ в C++:

Object object1; // Создается новый объект в СТЕКЕ
Object object2; // Создается еще один новый объект в СТЕКЕ
object1 = object2; // !!!! Это другое! СОДЕРЖИМОЕ object2 КОПИРУЕТСЯ в object1,
// с использованием "оператора присваивания копированием", определения оператора =.
// Но оба объекта все еще разные. Измените один – другой останется неизменным.
// Кроме того, объекты автоматически удаляются, когда функция возвращает управление...

Лучше всего думать об этом так — более или менее — Java (неявно) работает с указателями на объекты, в то время как C++ может работать либо с указателями на объекты, либо с самими объектами. Есть исключения из этого правила — например, если вы объявляете "примитивные" типы в Java, это фактические значения, которые копируются, а не указатели.

Так что,

Java:

int object1; // Целое число выделяется в стеке.
int object2; // Другое целое число выделяется в стеке.
object1 = object2; // Значение object2 копируется в object1.

Сказав это, использование указателей не обязательно является ни правильным, ни неправильным способом решения задач; однако другие ответы уже достаточно хорошо это осветили. Общая идея в том, что в C++ у вас гораздо больше контроля над временем жизни объектов и над тем, где они существуют.

Главный вывод – конструкция Object * object = new Object() на самом деле ближе всего к типичной семантике Java (или C#) в этом отношении.

0

В C++ объекты, выделенные в стеке (с помощью оператора Object object; внутри блока), будут существовать только в пределах области видимости, в которой они были объявлены. Когда блок кода завершает выполнение, объекты, объявленные в этом блоке, уничтожаются.

С другой стороны, если вы выделяете память в динамической памяти (куче), используя Object* obj = new Object(), они будут существовать на куче до тех пор, пока вы не вызовете delete obj.

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

0

Когда вы спрашиваете, зачем использовать такой подход, нужно рассмотреть, как это работает внутри тела функции.

Если вы объявляете объект следующим образом:

Object myObject;

то ваш myObject будет уничтожен, как только функция завершится. Это полезно, если вам не нужен объект за пределами функции. В этом случае объект размещается в стеке текущего потока.

Если же вы напишете внутри функции:

Object *myObject = new Object;

то экземпляр класса Object, на который указывает myObject, не будет уничтожен после завершения функции, и память будет выделена в куче.

Если вы программист на Java, то второй пример ближе к тому, как происходит выделение объектов в Java. Строка Object *myObject = new Object; эквивалентна Object myObject = new Object(); в Java. Разница заключается в том, что в Java myObject будет собран сборщиком мусора, тогда как в C++ он не освобождается автоматически; вам нужно явно вызвать delete myObject;, иначе у вас возникнут утечки памяти.

С C++11 вы можете использовать более безопасные способы динамического выделения памяти, такие как new Object, сохраняя значения в shared_ptr или unique_ptr.

std::shared_ptr<std::string> safe_str = std::make_shared<std::string>("make_shared");

// начиная с C++14
std::unique_ptr<std::string> safe_str = std::make_unique<std::string>("make_shared"); 

Кроме того, объекты очень часто хранятся в контейнерах, таких как map или vector, которые автоматически управляют временем жизни ваших объектов.

0

Технически, это вопрос выделения памяти, однако есть два практических аспекта, на которые стоит обратить внимание.

  1. Область видимости. Когда вы определяете объект без указателя, вы больше не сможете к нему обратиться после завершения блока кода, в котором он был определён. В то время как если вы определяете указатель с помощью оператора "new", вы можете обращаться к выделенной памяти из любой части программы, пока у вас есть указатель на эту память, и до тех пор, пока вы не вызовете "delete".

  2. Если вам нужно передать аргументы в функцию, вы хотите передавать указатель или ссылку для повышения эффективности. При передаче объекта происходит его копирование, и если это объект, занимающий много памяти, то это может потреблять значительные ресурсы ЦП (например, если вы копируете вектор, заполненный данными). В случае передачи указателя вы передаёте лишь одно значение типа int (в зависимости от реализации, но в большинстве случаев это одно int).

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

0

Когда у вас есть класс A, содержащий класс B, и вы хотите вызвать какую-либо функцию класса B из вне класса A, вам нужно получить указатель на экземпляр класса B. После этого вы сможете вызывать необходимые методы и, как следствие, изменять состояние объекта класса B, который находится в классе A.

Однако будьте осторожны с динамическими объектами. Если вы создаете объекты класса B динамически (например, с помощью оператора new), не забудьте правильно управлять памятью, чтобы избежать утечек. Убедитесь, что вы удаляете эти объекты, когда они больше не нужны, чтобы избежать неопределенного поведения программы.

Пример кода на C++ может выглядеть так:

class B {
public:
    void someFunction() {
        // Логика функции
    }
};

class A {
private:
    B* b; // Указатель на динамический объект класса B
public:
    A() {
        b = new B(); // Создание объекта B динамически
    }

    ~A() {
        delete b; // Освобождение памяти
    }

    B* getB() {
        return b; // Возвращаем указатель на объект B
    }
};

// Использование
A aInstance;
B* bInstance = aInstance.getB();
bInstance->someFunction();

В этом примере мы создаем объект класса B динамически внутри класса A и предоставляем доступ к его методам через указатель. Не забывайте следить за управлением памятью при использовании динамических объектов.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь