С++ Pointer: изменение содержимого без изменения адреса?

MyCustomObject * object=new MyCustomObject();

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

Я ошибаюсь, думая, что object = new MyCustomObject() даст новый адрес указателя на объект? Я хочу новый объект, не меняя адрес указателя (Да, я обязательно очищу старый объект).

Ответ 1

Объекты в памяти

Это вопрос понимания работы памяти для объектов на С++

Предположим, что у нас есть следующий объект:

class SimpleObject
{
public:
    char name[16];
    int age;
};

Размер будет 20. (На большинстве платформ). Поэтому в памяти это будет выглядеть так:

Bytes
name             age
0000000000000000|0000|

Вы можете изменить память вручную, чтобы создать объект, выполнив что-то вроде:

//Manual assigment
staticMemory[0] = 'F';
staticMemory[1] = 'e';
staticMemory[2] = 'l';
staticMemory[3] = 0;

int* age = reinterpret_cast<int*>(&staticMemory[16]);
*age = 21;

Вы можете доказать, что объект успешно создан, выполнив что-то вроде:

printf("In static manual memory the name is %s %d years old\n",
     reinterpret_cast<SimpleObject*>(staticMemory)->name
     ,reinterpret_cast<SimpleObject*>(staticMemory)->age);

Какие выходы:

В статической ручной памяти имя Fel 21 лет

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

Новый оператор

Новый оператор в основном работает следующим образом:

  • Выделить размер объекта в байтах в памяти кучи
  • Вызов конструктора объектов для заполнения памяти

Это сложнее в зависимости от реализации, но идея одинаков.

Итак, если конструктор:

SimpleObject::SimpleObject(const char* name,int age)
{
    memcpy(this->name,name,16);
    this->age = age;
}

Этот код:

SimpleObject* dynamicObject = new SimpleObject("Charles",31);

Будет почти эквивалентно:

SimpleObject* dynamicMemoryObject = (SimpleObject*)malloc( sizeof(SimpleObject) );
memcpy(dynamicMemoryObject->name,"Charles",16);
dynamicMemoryObject->age = 31;    

Как я уже сказал, это немного сложнее, но идея одна и та же.

Замена объекта в памяти

Теперь, когда это понятно, есть много способов заменить объект в том же пространстве памяти, наиболее распространенным способом является размещение new. Многие примеры ниже:

#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <new>

class SimpleObject
{
public:
    char name[16];
    int age;

    SimpleObject(const char* name,int age);
    SimpleObject()
    {
    }
};


SimpleObject::SimpleObject(const char* name,int age)
{
    memcpy(this->name,name,16);
    this->age = age;
}

//Object in static memory
SimpleObject staticObject;

//20 bytes in static memory
char staticMemory[20];

int main()
{
    //Manual assigment
    staticMemory[0] = 'F';
    staticMemory[1] = 'e';
    staticMemory[2] = 'l';
    staticMemory[3] = 0;

    int* age = reinterpret_cast<int*>(&staticMemory[16]);
    *age = 21;
    printf("In static manual memory the name is %s %d years old\n",
        reinterpret_cast<SimpleObject*>(staticMemory)->name
        ,reinterpret_cast<SimpleObject*>(staticMemory)->age);

    //Static object
    new (&staticObject) SimpleObject("John",23);
    printf("In static object the name is %s\n",staticObject.name);

    //Static memory
    SimpleObject* staticMemoryObject = reinterpret_cast<SimpleObject*>(staticMemory);
    new (staticMemoryObject) SimpleObject("Jenny",21);
    printf("In static memory the name is %s\n",staticMemoryObject->name);

    //Dynamic memory (heap)
    void* dynamicMemoryObject = malloc( sizeof(SimpleObject) );
    new (dynamicMemoryObject) SimpleObject("Xavier",22);
    printf("In dynamic memory the name is %s\n",reinterpret_cast<SimpleObject*>(dynamicMemoryObject)->name);
    free(dynamicMemoryObject);

    //Dynamic object
    SimpleObject* dynamicObject = new SimpleObject("Charles",31);
    printf("In a dynamic object the name is %s\n",dynamicObject->name);
    printf("Pointer of dynamic object is %8X\n",dynamicObject);

    //Replacing a dynamic object with placement new
    new (dynamicObject) SimpleObject("Charly",31);
    printf("New name of dynamic object is %s\n",dynamicObject->name);
    printf("Pointer of dynamic object is %8X\n",dynamicObject);

    //Replacing a dynamic object with stack object
    SimpleObject stackObject("Charl",31);
    memcpy(dynamicObject,&stackObject,sizeof(SimpleObject));
    printf("New name of dynamic object is %s\n",dynamicObject->name);
    printf("Pointer of dynamic object is %8X\n",dynamicObject);

    //Replacing a dynamic object with a new allocation
    dynamicObject = new SimpleObject("Sandy",22);
    printf("New name of dynamic object is %s\n",dynamicObject->name);
    printf("Pointer of dynamic object is %8X\n",dynamicObject);

    return 0;
}

С выходом:

В статической ручной памяти имя Fel 21 лет

В статическом объекте имя John

В статической памяти имя Jenny

В динамической памяти имя Xavier

В динамическом объекте имя Charles

Указатель динамического объекта - 4F8CF8

Новое имя динамического объекта Charly

Указатель динамического объекта - 4F8CF8

Новое имя динамического объекта - Charl

Указатель динамического объекта - 4F8CF8

Новое имя динамического объекта - Sandy

Указатель динамического объекта - FD850

Ответ 2

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

*object = MyCustomObject(); // Replace object with the result of default constructor.

Вместо конструктора по умолчанию вы можете использовать существующий экземпляр объекта (например, некоторый статический объект, который вы определяете для этой цели), или результат функции, которая возвращает нужный объект.

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

#include <iostream>

class MyObject
{
public:
    MyObject()  { std::cout << "I was created at " << (void *) this << ".\n"; }
    ~MyObject() { std::cout << "Farewell from "    << (void *) this << ".\n"; }
};


int main(void)
{
    // Allocate space and create a new object.
    MyObject *p = new MyObject;

    // Destroy the object but leave the space allocated.
    p->~MyObject();

    // Create a new object in the same space.
    p = new (p) MyObject;

    // Delete the object and release the space.
    delete p;

    return 0;
}

После вызова деструктора и размещения new указатели на старый объект указывают на новый объект, так как он находится в том же месте, что и старый объект.

Ответ 3

Я думаю, что у вас возникают трудности с понятиями указателя и объекта.

Объект - это экземпляр некоторого типа. Будь это базовый тип, такой как int или определенный пользователем тип, например struct или class.

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

Указатель указывает на объект (обычно). Подумайте об этом, как кусок веревки, прикрепленный к вашей футболке на одном конце, и у вас есть другой. Это позволяет вам вытащить футболку и делать с ней все. К этой футболке можно прикреплять несколько кусочков струны с разными людьми, держащими каждую деталь, позволяя каждому человеку вытащить ее и использовать. Помните, есть только одна футболка, всего несколько указателей на нее.

Копирование указателя похоже на то, что новый кусок строки прикреплен к объекту. Вы делаете это, назначая один указатель на другой tshirt* x = new tshirt(); tshirt* y = x;

Указатель хотя и немного опасен, потому что он фактически не может указывать на что-либо. Обычно, когда программист хочет признать, что указатель недействителен, он присваивает ему значение NULL, значение которого равно 0. В С++ 11 вместо NULL следует использовать nullptr для причин безопасности по типу.

Кроме того, если вы используете оператор delete на указателе, вы удаляете объект, на который он указывает. Указатель, который вы только что удалили, и любая копия этого указателя, как говорят, свисают, а это означает, что он указывает на ячейку памяти, в которой фактически нет действительного объекта. Как программист, вы должны установить значение этих указателей на NULL/nullptr, или вам будет сложно отслеживать ошибки в вашем коде.

Проблема с обвисшим указателем может быть уменьшена с помощью интеллектуальных указателей, таких как std:: unique_ptr и других (если вы переходите по этой ссылке, наведите указатель мыши на "Динамическое управление памятью", чтобы получить информацию о дополнительных оболочках указателей). Эти обертки пытаются и не позволяют вам непреднамеренно создавать оборванные указатели и утечки памяти.

Утечка памяти - это когда вы создаете объект с помощью оператора new, а затем теряете указатель на него. Возвращаясь к стопке аналогий одежды, это будет похоже на то, что вы уронили свой кусочек струны и, таким образом, забыли, что там есть футболка, поэтому вы выходите и покупаете другую. Если вы продолжаете это делать, вы обнаружите, что ваша куча одежды может заполнить комнату и в конечном итоге привести к взрыву комнаты, поскольку у вас больше нет места для большего количества футболок.

Итак, чтобы вернуться к вашему вопросу, чтобы изменить содержимое объекта, созданного с помощью оператора new, вы можете разыменовать этот указатель, используя оператор косвенности (*), или вы можете вызвать функцию-член или получить/задайте значение элемента объекта с помощью оператора разнесения структуры (- > ). Вы правы, что object = new MyCustomObject() предоставит вам новый адрес указателя, потому что он создал новый объект. Если вы просто хотите, чтобы новый указатель указывал на один и тот же объект, вы бы сделали что-то вроде этого:

MyCustomObject* pObject1 = new MyCustomObject();

// ... do some stuff ...

pObject1->doStuff();
(*pObject1).doMoreStuff();

pObject1->value = 3;
(*pObject1).value = 4;

// ... do some stuff ...

// This copies the pointer, which points at original object instance
MyCustomObject* pObject2 = pObject1;

// Anything done to object pointed at by pObject2 will be seen via going
// through pointer pObject1.

pObject2->value = 2;
assert(pObject1->value == 2); // asserting that pObject1->value == pObject2->value

Обратите внимание, что я префиксное имя переменной с помощью p, я (и других) использует это, чтобы я мог сразу определить, является ли переменная объектом или указателем на объект.

Объекты могут быть созданы непосредственно в стеке вызовов функций без ключевого слова new.

MyCustomObject object1;    // Note: no empty parenthesis ().
MyCustomObject object2(1); // Only use parenthesis if you actually are passing parameters.

Эти объекты автоматически уничтожаются при завершении вызова функции (или в некоторых случаях раньше).

Дополнительно

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

Были ссылки на размещение new, которые повторно используют память, которая уже была выделена. Это действительная, но довольно продвинутая функция. Вы должны быть осторожны, чтобы вызвать деструктор через указатель (т.е. pObject1->~MyCustomObject()) перед тем, как сделать новый (т.е. pObject1 = new (pObject1) MyCustomObject()), в противном случае вы можете иметь утечку ресурсов (файлы или могут быть оставлены открытыми или другими ресурсами могут быть не очищается).

Удачи.

Ответ 4

Указатель - это объект, поэтому, если вы делаете new Object(), вы создаете новый, но функции с указателем prevoius не будут видеть его. Если вы хотите изменить контент, вы можете сделать это, потому что все другие объекты, которые знают указатель, будут ссылаться на значения, которые содержит ваш объект. Это немного похоже на глобальную переменную. Таким образом, вы можете обойти указатель, и все функции будут видеть ваши изменения, как только они присоединятся к объекту.

Ответ 5

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

Единственное, что вы можете сделать, это изменить свойства объекта по определенному адресу, например, номер, сохраненный в переменной int.

Вы правы, оператор new возвращает новый адрес или позволяет указать адрес, который система решила.

Ответ 6

Внедрить распределитель для возврата одного и того же адреса и использовать его в новом и удалить оператор с перегрузкой.