Безопасно ли использовать новое размещение на указателе ‘this’?

Текущая реализация

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

class ResourceManager {
ResourceManager() {}

ResourceManager(A* a_ptr) :
b_ptr(new B(a)),
c_ptr(new C(b_ptr.get())) {}

ResourceManager& operator=(ResourceManager&& that) {
// Call destructor, then construct a new instance on top
~ResourceManager();
ResourceManager* new_this = new(this) ResourceManager();

// Surely this must be the case, right?
// Is there any reason to prefer using either?
assert(new_this == this);

new_this->b_ptr = that.b_ptr;
new_this->c_ptr = that.c_ptr;

return *new_this;
}

unique_ptr<B> b;
unique_ptr<C> c;
};

Случай использования

В данном случае я хочу переназначить новые значения указателям, сохраняя при этом ResourceManager как переменная, размещаемая в стеке, или как не указывающий член класса.

С моей текущей установкой я представляю себе, что-то вроде этого:

A a, another_a;
ResourceManager r(&a);

// Use r...

// Destroy old ResourceManager and create the new one in place.
r = ResourceManager(&another_a);

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

Уродливая альтернатива

Альтернативным более уродливым (и опасным) методом было бы явное reset unique_ptr поля принципиально в обратном порядке (помните, что C зависит от B, и, следовательно, должен быть уничтожен первым), эффективно имитируя поведение уничтожения по умолчанию.

ResourceManager& operator=(ResourceManager&& that) {
// Mimic destructor call (reverse-order destruction)
c_ptr.reset();
b_ptr.reset();

b_ptr = that.b_ptr;
c_ptr = that.c_ptr;

return *this;
}

Обратите внимание, что неправильно реализация будет просто использовать оператор присваивания по умолчанию для ResourceManager, Это назначит поле с целью что подразумевает в порядке уничтожения unique_ptrs, тогда как мы требуем уничтожения в обратном порядке.

Вопросы

Это использование this указатель с размещением new а явный деструктор вызывать безопасно?

Должен ли я использовать возвращенный new_this указатель в отличие от оригинала this указатель (например, если this технически указатель становится недействительным после вызова деструктора)?

Есть ли какие-либо лучшие предложения для достижения этой цели? Если добавить больше таких unique_ptr Поля к классу, я должен был бы убедиться, что я добавляю копию в оператор присваивания. Например, можно ли вместо этого вызвать конструктор перемещения, например:

ResourceManager& operator=(ResourceManager&& that) {
// Call destructor
~ResourceManager();

// Move-construct a new instance on top
ResourceManager* new_this = new(this) ResourceManager(that);
return *new_this;
}

5

Решение

Ваше решение кажется слишком сложным.

Я бы написал это так:

class ResourceManager {
ResourceManager() {}

ResourceManager(A* a_ptr) :
b_ptr(new B(a)),
c_ptr(new C(b_ptr.get())) {}

ResourceManager& operator=(ResourceManager&& that)
{
// the order of these moves/assignments is important
// The old value of *(this->c_ptr) will be destroyed before
// the old value of *(this->b_ptr) which is good because *c_ptr presumably
// has an unprotected pointer to *b_ptr.
c_ptr = std::move(that.c_ptr);
b_ptr = std::move(that.b_ptr);
//  (a better solution might be to use shared_ptr<B> rather than unique_ptr<B>
return *this;
}

unique_ptr<B> b_ptr;
unique_ptr<C> c_ptr;
};

Примечание. Когда задание на перемещение возвращается, that будет «пустой», означающий оба that.b_ptr а также that.c_ptr являются nullptr, Это ожидаемый результат назначения перемещения.

Или, если важно «восстановить» цель назначения (при условии, что в этом примере не показан дополнительный код, который делает это так), я мог бы добавить конструктор перемещения и метод подкачки следующим образом:

 ResourceManager(ResourceManager&& that)
: b_ptr(std::move(that.b_ptr)),
c_ptr(std::move(that.c_ptr))
{
}

void swap(ResourceManager & that)
{
b_ptr.swap(that.b_ptr);
c_ptr.swap(that.c_ptr);
}

ResourceManager& operator=(ResourceManager&& that)
{
ResourceManager temp(std::move(that));
this->swap(temp);
return *this;
}
4

Другие решения


По вопросам рекламы [email protected]