Отказ от ответственности: Цель исследования состоит в том, чтобы отключить оптимизацию копирования и возвращаемого значения для предоставленной части кода.Пожалуйста, избегайте ответов, если хотите упомянуть что-то вроде XY-проблемы.Вопрос носит сугубо технический и исследовательский характер и сформулирован строго таким образом:
В C ++ 14 была введена оптимизация копирования и возврата значений.Если какой-либо объект был разрушен и сконструирован для копирования в одном выражении, например, назначение-копирование или немедленное возвращение значения из функции по значению, то конструктор копирования исключается.
Следующие рассуждения применяются к конструктору копирования, но аналогичные рассуждения могут быть выполнены для конструктора перемещения, поэтому далее это не рассматривается.
Существуют некоторые частичные решения для отключения разрешения копирования для пользовательского кода:
1) Опция, зависящая от компилятора.Для GCC существует решение, основанное на конструкциях __attribule__
или #pragma GCC
, например https://stackoverflow.com/a/33475393/7878274.Но поскольку он зависит от компилятора, он не отвечает на вопрос.
2) Конструктор копирования, отключающий принудительное выполнение, например Clazz(const Clazz&) = delete
.Или объявите конструктор копирования как explicit
, чтобы предотвратить его использование.Такое решение не отвечает задаче, поскольку оно меняет семантику копирования и вынуждает вводить пользовательские функции, такие как Class::copy(const Clazz&)
.
3) Используя промежуточный тип, как описано здесь https://stackoverflow.com/a/16238053/7878274.Поскольку это решение вынуждает вводить новый тип потомка, оно не встречает вопроса.
После некоторых исследований было обнаружено, что восстановление временного значения может решить вопрос.Если с помощью этого класса повторно интерпретировать исходный класс как ссылку на одноэлементный массив и извлечь первый элемент, то исключение копирования будет отключено.Функция шаблона может быть написана так:
template<typename T, typename ... Args> T noelide(Args ... args) {
return (((T(&)[1])(T(args...)))[0]);
}
Такое решение хорошо работает в большинстве случаев.В следующем коде он генерирует три вызова конструктора копирования - один для прямого копирования-назначения и два для назначения с возвратом из функции.Он хорошо работает в MSVC 2017
#include <iostream>
class Clazz {
public: int q;
Clazz(int q) : q(q) { std::cout << "Default constructor " << q << std::endl; }
Clazz(const Clazz& cl) : q(cl.q) { std::cout << "Copy constructor " << q << std::endl; }
~Clazz() { std::cout << "Destructor " << q << std::endl; }
};
template<typename T, typename ... Args> T noelide(Args ... args) {
return (((T(&)[1])(T(args...)))[0]);
}
Clazz func(int q) {
return noelide<Clazz>(q);
}
int main() {
Clazz a = noelide<Clazz>(10);
Clazz b = func(20);
const Clazz& c = func(30);
return 0;
}
Этот подход хорошо работает для a
и b
случаев, но выполняет избыточное копирование с регистром c
- вместо копирования ссылка на временное должно возвращаться спродление жизни.
Вопрос : как изменить шаблон noelide
, чтобы он нормально работал с const lvalue-reference с расширением времени жизни?Спасибо!