Отключить копирование в C ++ - PullRequest
6 голосов
/ 03 апреля 2019

Отказ от ответственности: Цель исследования состоит в том, чтобы отключить оптимизацию копирования и возвращаемого значения для предоставленной части кода.Пожалуйста, избегайте ответов, если хотите упомянуть что-то вроде 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 с расширением времени жизни?Спасибо!

Ответы [ 2 ]

3 голосов
/ 03 апреля 2019

Согласно N4140, 12.8.31:

...

Это исключение операций копирования / перемещения, называемое разрешением копирования, допускается при следующих обстоятельствах (которые могут быть объединены исключить несколько копий):

(31.1) - в операторе возврата в функции с типом возврата класса, когда выражение является именем энергонезависимого автоматического объекта (кроме функции или параметра catch-условия) с тем же cv-неквалифицированный тип как тип возвращаемой функции, копирование / перемещение операция может быть опущена путем непосредственного создания автоматического объекта в возвращаемое значение функции

(31.3) - когда временный объект класса, который не был связан с ссылка (12.2) будет скопирована / перемещена в объект класса с тем же cv-unqualified type, операция копирования / перемещения может быть опущена строительство временного объекта непосредственно в цель опущено копирование / перемещение

Так что, если я правильно понимаю, исключение копирования может произойти только в том случае, если оператор return является именем локальной переменной. Так, например, вы можете «отключить» копирование, вернув, например, return std::move(value) ... Если вам не нравится использовать move для этого, вы можете просто реализовать noelide как static_cast<T&&>(...).

3 голосов
/ 03 апреля 2019

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

Вы можете предотвратить обязательное применение RVO, нарушив одно из требований, но вы не можете надежно предотвратить необязательную разрешенную оптимизацию.На данный момент все, что вы делаете - это изменяете семантику или специфичную для компилятора.

...