Есть ли способ назначить сложенный объект выделенной памяти с помощью нового размещения?

Вот моя программа:

#include <iostream>

using namespace std;

class Object {
public:
    Object() { cout << "Object constructor!" << endl; }
    ~Object() { cout << "Object destructor!" << endl; }
    Object(const Object& obj) { information = obj.information; cout << "Copy constructor!" << endl; }
    void setInformation(const string info) { information = info; }
    string getInformation() const { return information; }
private:
    string information;
};

class Storage {
public:
    Storage() { object = static_cast<Object*>(operator new(sizeof(Object))); }
    ~Storage() { operator delete(object); }

    void setObject(const Object& obj) {
        // Todo: assign obj to the allocated space of the pointer object
    }
private:
    Object* object;
};

int main()
{
    Object o;
    o.setInformation("Engine");
    Storage storage;
    storage.setObject(o);
    return 0;
}

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

object = new(object) Object()

построить объект. Но могу ли я поместить в память уже созданный объект? В моем случае вызовите метод setObject (). Если да, с какими проблемами я могу столкнуться при таком управлении памятью? Заранее спасибо.

Проблем здесь много. Помимо того, что Storage нарушает само правило трех, если setObject никогда не вызывается, деструктор пытается delete что-то, что не было создано как экземпляр объекта. Кабум.

Sam Varshavchik 31.10.2018 12:04

Вам нужно где-то разместить свой объект, и нет причин, по которым объект из setObject был размещен в стеке, и у вас нет возможности получить его, поскольку он const. Таким образом, вы не можете назначить его своему указателю. Используйте unique_ptr, чтобы сохранить объект и создать новый, когда у вас есть вызов setObject. Если вы не хотите создавать новый объект, передайте unique_ptr. Четко говорите об управлении памятью.

Matthieu Brucher 31.10.2018 12:05

Сэм, я знаю это, но спасибо, что указали на это. Программа еще не закончена. И поэтому я решил сначала задать конкретный вопрос: как заполнить метод setObject (). Я знаю, что позже мне придется явно вызвать деструктор.

Oleg 31.10.2018 12:08

Вы ограничены C++ 11?

r3mus n0x 31.10.2018 12:17

Нет, я не ограничен. Насколько я понимаю, в C++ 11 этого сделать нельзя, верно?

Oleg 31.10.2018 12:21
1
5
76
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Самый простой способ добиться желаемого поведения - использовать std::optional из C++ 17. Это позволит вам «зарезервировать» память для вашего Object, не создавая ее и без использования кучи. Он также будет обрабатывать все вызовы конструктора и деструктора.

Это также можно сделать без std::optional, но вам придется реализовать подобное решение самостоятельно. В любом случае вам понадобится дополнительный член, чтобы указать, создан ли объект или нет, чтобы вы могли правильно обрабатывать уничтожение и присвоение.

Тогда ваш метод setObject будет выглядеть примерно так:

void setObject(const Object& obj) {
    if (constructed) {
        *object = obj;
    } else {
        new (object) Object(obj);
        constructed = true;
    }
}

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

void setObject(Object obj) {
    if (constructed) {
        *object = std::move(obj);
    } else {
        new (object) Object(std::move(obj));
        constructed = true;
    }
}

Обратите внимание, что теперь setObject принимает параметр по значению, поэтому его можно использовать со ссылками как lvalue, так и rvalue для создания параметра, который затем будет перемещен в член.

Спасибо, да, что я должен был сделать потом.

Oleg 31.10.2018 12:32

@Oleg, все равно было бы лучше просто использовать std::optional, но если вы хотите сделать это самостоятельно, вы можете избавиться от динамического распределения с помощью буфера char buffer[sizeof(Object)] или более элегантным решением было бы использовать union { Object obj; } с одним членом.

r3mus n0x 31.10.2018 12:37

Правильно ли я, что если я использую setObject, который вы написали, мне нужно сделать следующее: if (built) (* object). ~ Object (); в деструкторе?

Oleg 31.10.2018 12:53

@Oleg, да и можно использовать object->~Object(); для лучшей читаемости.

r3mus n0x 31.10.2018 12:57

Есть ли способ сделать то же самое при удалении конструктора копирования?

Oleg 31.10.2018 13:09

@Oleg, вы можете использовать конструктор перемещения, если он доступен. Вам нужно будет изменить подпись setObject, чтобы принимать ссылку rvalue или принимать по значению, а затем использовать std::move в теле.

r3mus n0x 31.10.2018 13:12

Спасибо, я хотел глубже разобраться в управлении памятью. Но я обязательно буду использовать C++ 17 (std :: optional) для такого рода задач.

Oleg 31.10.2018 13:24

@ r3musn0x char buffer[sizeof(Object)] неверен, так как не гарантирует правильного выравнивания. Вместо этого используйте, например, std::aligned_storage.

Daniel Langr 31.10.2018 13:43

Размещение нового - довольно сложная конструкция, и мысль о наличии Object*, который на самом деле указывает на неинициализированную память, довольно пугает. Могу я предложить вам облегчить себе жизнь?

class Storage {
    void setObject(const Object& obj) {
        if (object) {
            *object = obj;
        } else {
            object.reset(new Object(obj));
        }
    }
private:
    std::unique_ptr<Object> object;
};

Для этого требуются конструктор копирования и оператор присваивания в классе Object, но это гораздо более идиоматичный C++. А в реализации Object, как показано, реализации по умолчанию, предоставляемые компилятором, уже подходят.

Просто для уточнения:

But can I put in the memory an object that is already created?

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

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

Другие вопросы по теме