О времени стоит, что дороже: движение или перезапись? C++11
Допустим, у меня есть стек "неиспользуемых" динамических объектов. Каждый раз, когда мне нужен новый объект, я использую один из этих "неиспользуемых" объектов в качестве заполнителя для новой петиции. Что-то вроде:
template<typename T>
class my_list
{
public:
template<typename... Args>
T* newObject(Args&&... args)
{
if (unused.empty())
return new T(forward<Args>(args)...);
else {
auto* newbie = unused.top();
unused.pop();
newbie = new (newbie) T(forward<Args>(args)...); // l. X
return newbie;
}
}
private:
stack<T*> unused;
};
Вопрос в строке X, что является более эффективным, письменное предложение или:
*newbie = std::move(T(forward<Args>(args)...));
Это означает, что то, что более эффективно по времени, - вызов new
с newbie
как адрес (избегая петиций новой памяти) или просто движение нового временного объекта, перезаписывающего оригинал?
2 ответа
move
не требуется, так как вызов конструктора создает временное значение prvalue. Ваш вопрос сводится к тому, что является более эффективным:
newbie->~T();
new (newbie) T(std::forward<Args>(args)...);
или же
*newbie = T(std::forward<Args>(args)...);
Если предположить, T
имеет правильно написанный оператор присваивания перемещения, вероятно, будет небольшая разница, если таковая имеется; с точки зрения побочных эффектов деструктора T
вызывается перед конструктором в первом случае и после во втором, но нет никаких оснований полагать, что это повлияет на производительность в одну сторону больше, чем в другую. Последнее может привести к получению более примитивных копий, но любой приличный оптимизирующий компилятор их устранит.
В отсутствие тестирования производительности, показывающего, что первое значительно повышает производительность, вы должны предпочесть второе, так как оно гораздо более читабельно и проще сделать исключение безопасным.
Вот еще один жизнеспособный вариант. Преимущество состоит в том, что, хотя C++11 все еще требуется для идеальной пересылки, он работает с библиотечным кодом, который не был обновлен для реализации построения и назначения перемещения.
T(forward<Args>(args)...).swap(*newbie);
Кроме того, ваш вызов присваивания перемещения уже имеет временный объект, нет необходимости явно делать его подвижным.
*newbie = T(forward<Args>(args)...);
Любой из них значительно облегчит обеспечение безопасности исключений, чем подход "сначала уничтожить, а затем построить на месте".