Размещение нового на не указательных переменных и членах класса - PullRequest
2 голосов
/ 07 октября 2019

Рассмотрим следующий пример:

#include <iostream>

struct A {

    int i;

    A(int i)
    {
        this->i = i;
    }

    A &operator=(const A &a) = delete;
    A(const A &a) = delete;
};

int main()
{
    A a(1);
    new(&a) A(5);
    //a = A(7); // not allowed since = is deleted in A
    std::cout << a.i << std::endl;
}

Это простой пример с использованием оператора размещения new. Поскольку конструктор копирования и оператор присваивания struct A были удалены (по какой-либо причине), изменить объект, который хранится в переменной A a, невозможно, за исключением передачи его адреса оператору размещения new.

Причины для этого могут быть связаны с тем, что struct A содержит большие массивы (например, 100M записей), которые необходимо будет скопировать в операторе присваивания и в конструкторе копирования.

Первая часть вопросавращается вокруг "законности" этого подхода. Я нашел этот вопрос stackoverflow, принятый ответ на который говорит

, это совершенно законно. И бесполезно, потому что вы не можете использовать var [A a в этом случае] для ссылки на состояние [объекта], который вы сохранили в нем после нового размещения. Любой такой доступ является неопределенным поведением. […] Ни при каких обстоятельствах вы не можете ссылаться на var после размещения new'd поверх него.

Почему это так? Я видел несколько других примеров для оператора размещения new, которые всегда похожи на

A a(1);
A *b = new(&a) A(2);
// Now use *b instead of a

Насколько я понимаю, не имеет значения, используется ли A a или A *b для доступа к объекту с момента размещенияnew заменяет объект по адресу A a, который, конечно, равен A a. То есть я бы ожидал, что всегда b == &a. Возможно, ответ был недостаточно ясен, и это ограничение связано с константностью члена класса.

Вот еще один пример с той же идеей, однако на этот раз struct A встроен в другой объект:

#include <iostream>

struct A {

    int *p;

    A(int i)
    {
        p = new int(i);
    }

    ~A()
    {
        delete p;
    }

    A &operator=(const A &a) = delete;
    A(const A &a) = delete;
};

struct B {

    A a;

    B(int i) : a(i)
    {
    }

    void set(int i)
    {
        a.~A(); // Destroy the old object
        new(&a) A(i);
    } 

};

int main()
{
    B b(1);
    b.set(2);
    std::cout << *(b.a.i) << std::endl;
    // This should print 2 and there should be no memory leaks
}

Вопрос в основном тот же с тем же рассуждением. Допустимо ли размещение новых на адрес &a?

1 Ответ

3 голосов
/ 07 октября 2019

С этим конкретным кодом вы в порядке и можете использовать a для ссылки на новый объект, который вы поставили на его место. Это покрывается [basic.life] / 8

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

  • хранилище для нового объекта точно перекрывает место хранения, котороезанят исходный объект, и

  • новый объект того же типа, что и исходный объект (без учета cv-квалификаторов верхнего уровня), и

  • тип исходного объекта не является константно-квалифицированным, и, если тип класса, не содержит какого-либо нестатического члена данных, тип которого является константно-квалифицированнымence type и

  • ни исходный объект, ни новый объект не являются потенциально перекрывающимися подобъектами ([intro.object]).

выделение мое

Вы отметите все эти требования, чтобы a ссылался на "новый" A, который вы поместили в память a.

...