Инициализация копирования с удаленным конструктором копирования при инициализации ссылки

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

#include <iostream>
class Data{
public:
    Data() = default;
    Data(Data const&) = delete;
    Data(int) {

    }
};
int main(){
  int a = 0;
  const std::string& rs = "abc"; // rs refers to temporary copy-initialized from char array
  Data const& d_rf = a;          // #2 but here can be complied
  // accroding to the standard, the reference in #2 is bound to a temporary object, the temporary is copy-initialized from the expression
}

[dcl.init.ref]

Если T1 или T2 относятся к типу класса, а T1 не связан со ссылкой на T2, пользовательские преобразования рассматриваются с использованием правил для инициализации копирования объекта типа «cv1 T1» пользователем. определенное преобразование ([dcl.init], [over.match.copy], [over.match.conv]); программа будет сформирована неправильно, если соответствующая не ссылочная инициализация копии будет неправильно сформирована. Результат вызова функции преобразования, как описано для инициализации копии без ссылки, затем используется для прямой инициализации ссылки. Для этой прямой инициализации определенные пользователем преобразования не рассматриваются.

Копировать инициализацию

В противном случае (т. Е. Для остальных случаев инициализации копирования) определяемые пользователем преобразования, которые могут преобразовывать исходный тип в целевой тип или (когда используется функция преобразования) в его производный класс, перечисляются, как описано в [over. match.copy], а лучший выбирается путем разрешения перегрузки ([over.match]). Если преобразование не может быть выполнено или неоднозначно, инициализация сформирована неправильно. Выбранная функция вызывается с выражением инициализатора в качестве аргумента; если функция является конструктором, вызов является prvalue неквалифицированной cv версии целевого типа, чей объект результата инициализируется конструктором. Вызов используется для прямой инициализации в соответствии с приведенными выше правилами объекта, который является адресатом инициализации копии.

Согласно стандарту тип a - int, а тип инициализированной ссылки - Data, поэтому от int до Data, определяемые пользователем преобразования рассматриваются с использованием правил для инициализации копирования объекта типа «cv1 T1» посредством пользовательского преобразования. Это означает, что Data const& d_rf = a; можно перевести на Data temporary = a; Data const& d_rf = temporary;. Для Data temporary = a;, несмотря на то, что исключение копирования существует, конструктор копирования / перемещения необходимо проверить, доступен ли он, но конструктор копирования для class Data имеет был удален, почему это можно сделать?

Вот некоторые цитаты из стандартных
Скопируйте инициализацию ссылки из enseignement

Копировать инициализацию ссылки из cppreference

Если ссылка является ссылкой на lvalue:

Если объект является выражением lvalue, а его тип - T или производный от T, и в равной или меньшей степени квалифицирован cv, то ссылка привязывается к объекту, идентифицированному lvalue, или к его подобъекту базового класса.
Если объект является выражением lvalue, и его тип неявно преобразуется в тип, который является либо T, либо производным от T, равным или менее квалифицированным cv, тогда неявные функции преобразования исходного типа и его базовые классы, которые возвращают ссылки lvalue, являются рассматривается и выбирается лучший по разрешению перегрузки. Затем ссылка привязывается к объекту, идентифицированному lvalue, возвращаемым функцией преобразования (или с подобъектом ее базового класса).

В противном случае, если ссылка является либо ссылкой rvalue, либо ссылкой lvalue на const:

Если объект является значением xvalue, классом prvalue, массивом prvalue или типом lvalue функции, который является либо T, либо производным от T, равным или менее квалифицированным cv, то ссылка привязана к значению выражения инициализатора или к его базовый подобъект.
Если объект является выражением типа класса, которое может быть неявно преобразовано в значение xvalue, класс prvalue или значение функции типа, которое является либо T, либо производным от T, равным или менее квалифицированным cv, то ссылка привязана к результату преобразования или к его базовому подобъекту.
В противном случае создается временный объект типа T и инициализируется копией из объекта. Затем ссылка привязывается к этому временному объекту. Применяются правила инициализации копирования (явные конструкторы не рассматриваются).
[пример:
const std :: string & rs = "abc"; // rs относится к временному инициализированному копией из массива символов]

ОБНОВИТЬ:

Мы рассматриваем код под N337.

согласно стандарту тип значения a - int, а тип назначения, на который ссылается ссылка, - Data, поэтому компилятору необходимо сгенерировать временный объект типа Data с помощью инициализации копии. Здесь нет никаких сомнений , поэтому мы сосредотачиваемся на инициализации копии. Тип источника - int, а тип назначения - Data, эта ситуация соответствует:

В противном случае (т. Е. Для остальных случаев копирования-инициализации) определяемые пользователем последовательности преобразования, которые могут преобразовывать из исходного типа в целевой тип или (когда используется функция преобразования) в его производный класс, перечисляются, как описано в 13.3. 1.4, а лучший выбирается по разрешению перегрузки (13.3). Если преобразование не может быть выполнено или неоднозначно, инициализация сформирована неправильно. Выбранная функция вызывается с выражением инициализатора в качестве аргумента; если функция является конструктором, вызов инициализирует временную версию cv-неквалифицированной версии целевого типа. Временное - это prvalue. Результат вызова (который является временным для случая конструктора) затем используется для прямой инициализации, в соответствии с приведенными выше правилами, объекта, который является адресатом инициализации копирования. В некоторых случаях реализации разрешается исключить копирование, присущее этой прямой инициализации, путем построения промежуточного результата непосредственно в инициализируемом объекте;

ОБРАТИТЕ ВНИМАНИЕ, что выделенная жирным шрифтом часть не означает, что значение int напрямую инициализирует временное значение Data::Data(int). Это означает, что int сначала преобразуется в Data с помощью Data::Data(int), затем этот результат напрямую инициализирует временный объект, который здесь является объектом, являющимся адресатом инициализации копии. Если мы используем код для обозначения жирной части, это похоже на Data temporary(Data(a)).

Вышеупомянутые правила здесь:

- Если инициализация является прямой инициализацией или копией-инициализацией, когда cv-неквалифицированная версия исходного типа является тем же классом, что и класс назначения, или производным классом от него, рассматриваются конструкторы. Применимые конструкторы перечислены (13.3.1.3), и лучший из них выбирается посредством разрешения перегрузки (13.3). Выбранный таким образом конструктор вызывается для инициализации объекта с выражением инициализатора или списком выражений в качестве аргумента (ов). Если конструктор не применяется или разрешение перегрузки неоднозначно, инициализация сформирована неправильно.

Вернитесь к Data temporary(Data(a)). Очевидно, что конструктор копирования / перемещения лучше всего подходит для аргумента Data (a). Однако Data(Data const&) = delete;, поэтому конструктор копирования / перемещения недоступен. Почему поставщик не сообщает об ошибке?


person xmh0511    schedule 20.01.2020    source источник
comment
Почему вы думаете, что инициализация копирования выполняется для строки №2. Я думаю, что в лучшем случае вызывается оператор &, но в вашем случае он не определен.   -  person Luka Rahne    schedule 20.01.2020
comment
Вы создаете const ref для существующего объекта, без копирования, без перемещения, без конструкции вообще ...   -  person Klaus    schedule 20.01.2020
comment
Обратите внимание, что #1 действителен с C ++ 17   -  person Jarod42    schedule 20.01.2020
comment
@ Klaus, исходный тип несовместим с целевым типом   -  person xmh0511    schedule 20.01.2020
comment
@LukaRahne из int в Data, требуется пользовательское преобразование   -  person xmh0511    schedule 20.01.2020
comment
@ Jarod42 говорит о вопросе в C ++ 11, #1 недействителен, но #2 действителен, почему?   -  person xmh0511    schedule 20.01.2020
comment
Вы удалили конструктор копирования, поэтому №1 будет сообщением об ошибке. В № 2 здесь просто псевдоним правой части a, а не создание объекта.   -  person Build Succeeded    schedule 20.01.2020
comment
@Mannoj Обратите внимание, тип источника несовместим с типом назначения   -  person xmh0511    schedule 20.01.2020
comment
stackoverflow.com/questions/39548639/ Но я думаю, вы удалили конструктор копирования, поскольку он сначала пытается преобразовать int в тип класса, найдя соответствующее преобразование, которое ожидалось в виде конструктора преобразования.   -  person Build Succeeded    schedule 20.01.2020
comment
@Mannoj #1 используется для проверки инициализированной копии, для которой нужен конструктор копирования, и рассмотрения #2, ссылка привязана к временному объекту, который инициализируется копией из a, но #2 выполняется без ошибок   -  person xmh0511    schedule 20.01.2020
comment
N3337 очень старый, было бы лучше придерживаться документов C ++ 17 или более поздних версий. Или, возможно, если вы собираетесь спросить конкретно о C ++ 11 (за исключением C ++ 17), добавьте C ++ 11 в качестве тега.   -  person M.M    schedule 24.01.2020
comment
@ M.M да, я добавил тег c ++ 11   -  person xmh0511    schedule 24.01.2020


Ответы (6)


Эта проблема устраняется проблемой 1604, и предлагаемое решение, похоже, подтверждает, что такой код должен быть неправильно сформирован, поэтому я бы счел это ошибкой компилятора.

К счастью, начиная с C ++ 17, этот код становится хорошо сформированным из-за гарантированного исключения копирования, что согласуется с компиляторами.

person xskxzr    schedule 02.02.2020
comment
да, c ++ 17 гарантирует исключение копии, но в том же компиляторе Data d = 0 все еще плохо сформирован, почему в этом случае не то же самое поведение с инициализацией копирования инициализирующей ссылки - person xmh0511; 04.02.2020
comment
@jackX Не знаю, может, нам нужно проверить логику компилятора, чтобы понять причину. - person xskxzr; 05.02.2020

Давайте посмотрим, что говорится в стандарте:

В противном случае создается временный объект типа T и инициализируется копией из объекта. Затем ссылка привязывается к этому временному объекту. Применяются правила инициализации копирования (явные конструкторы не рассматриваются).

Итак, временный объект типа T построен. Этот временный объект инициализируется копией из данного объекта. ОК ... как это работает?

Ну, вы процитировали правило, объясняющее, как будет работать инициализация копирования из заданного значения. Он попытается вызвать определенные пользователем преобразования, просеивая соответствующие конструкторы T и операторы преобразования для значения (а их нет, поскольку оно имеет тип int). На T есть неявный конструктор преобразования, который принимает объект типа int. Итак, этот конструктор вызывается для инициализации объекта.

Затем ссылка привязывается к этому временному объекту в соответствии с указанными вами правилами.

Ни разу нет попыток вызвать какую-либо из удаленных функций. Тот факт, что это называется «инициализация копирования», не означает, что будет вызван конструктор копии. Это называется «копирование-инициализация», потому что (обычно) вызывается знаком =, и поэтому выглядит как «копирование».

Причина, по которой Data d = a; не работает, заключается в том, что C ++ 11 определяет эту операцию, чтобы сначала преобразовать a во временный Data, а затем инициализировать d этим временным. То есть, по сути, это эквивалент Data d = Data(a);. Последняя инициализация (гипотетически) вызовет конструктор копирования, что приведет к ошибке.

person Nicol Bolas    schedule 23.01.2020
comment
@NicoBolas ключевой момент - от int до Data, неявное преобразование - это Data::Data(int) или что-то другое, если преобразование будет Data::Data(int), даже если нет copy/move contructor, это не имеет значения, но, как указано выше, стандарт, который я процитировал, T const& rf = object, если объект не является T или производным от T, объект формы преобразования в T должен использовать copy-initialization для генерации временного значения типа T, а затем ссылка привязывается к временному значению. Если я неправильно прочитал стандарт, пожалуйста поправьте меня с помощью соответствующего стандарта, спасибо - person xmh0511; 23.01.2020
comment
Насколько я понимаю, временный объект типа T создан и инициализируется копией из объекта T temporary = object, как вы сказали, инициализация копирования означает `` использование знака = '', вы согласны со мной? Теперь мы заменяем аргументы, результат - Данные временный = a, тогда ссылка привязана к temporary, поэтому для Data temporary = a, даже если существует copy-elision, необходимо проверить конструктор копирования / перемещения - person xmh0511; 23.01.2020
comment
@jackX: объект формы преобразования в T должен использовать инициализацию копирования для генерации временного значения типа T Конструктор копирования вызывается, когда вы пытаетесь создать объект типа T с некоторым lvalue типа T. У нас еще нет Data; все, что у нас есть, это int. Итак, мы копируем-инициализируем временный Data из int в соответствии с изложенными мною правилами. Ни в коем случае не предпринимается попытка вызвать конструктор копирования. - person Nicol Bolas; 23.01.2020
comment
Позвольте нам продолжить это обсуждение в чате. - person Nicol Bolas; 23.01.2020
comment
Здесь задействованы две временные конструкции. Создается временный t1 типа T, и ссылка привязывается к t1. Чтобы скопировать-инициализировать t1 из a, следует создать еще один временный t2 в соответствии с правилами, а затем t1 будет инициализирован напрямую из t2. Проблема возникает из-за процесса прямой инициализации t1 из t2. - person xskxzr; 07.02.2020

Принятый ответ выглядит неуместным; Последовательность настолько проста, насколько кажется. Никакого конструктора копирования / перемещения или оптимизации не требуется; все темы строго неактуальны. Временные «Данные» создаются из «int» с использованием преобразователя. Затем prvalue привязывается к ссылке lvalue 'const'. Это все. Если это не выглядит правильным, то мы обсуждаем разные языки программирования; Я, конечно, говорю о C ++.

PS: Я не могу ссылаться на стандарт, потому что не могу себе этого позволить.

ИЗМЕНИТЬ ================================

'=' - это еще один способ вызвать ctor с одним аргументом, не помеченный как «явный». Это то же самое, что и фигурные или круглые фигурные скобки - до тех пор, пока ctor принимает одиночные параметры, если ctor не является «явным». Никто не изучает программирование по стандартам чтения; Это для разработчиков компиляторов.

Лучшее, FM.

person Red.Wave    schedule 05.02.2020
comment
ключевым моментом является инициализация копирования (т.е. временные данные создаются из int) - person xmh0511; 05.02.2020
comment
Я просто не вижу проблемы. Это из-за ошибочной ссылки на объект или что? Даже с c99 и ctor частной копии (= синтаксис удаления не существует) код должен компилироваться. Если бы он не компилировался, я бы попросил предоставить информацию о новых правилах взлома кода. - person Red.Wave; 06.02.2020
comment
copy-initialization необходимо проверить, доступен ли конструктор копирования / перемещения в C ++ 11 - person xmh0511; 06.02.2020
comment
Большой НЕТ. Очевидно, ошибочно принимают объект за ссылку. Уберите амперсанд в этой строке, и мы сможем обсудить ситуацию. - person Red.Wave; 06.02.2020
comment
Я нашел ссылочные цитаты из стандарта, в котором, хотя и не хватает надлежащей компиляции причин (они разбросаны по большому объему документа), почему исключение не происходит в случае C ++ 14, в C ++ 11 этого не произойдет. - person Swift - Friday Pie; 07.02.2020
comment
Как вы интерпретируете, что Data d = 0; неправильно сформирован? - person xskxzr; 07.02.2020
comment
Это не плохо. Он должен компилироваться. Вызов простого конвертирующего конструктора. - person Red.Wave; 07.02.2020

Я склонен согласиться с @ Red.Wave - временный объект создается с использованием Data :: Data (int), а затем ссылка "d_rf" инициализируется его адресом. Конструктор копирования здесь вообще не задействован.

person ivan.ukr    schedule 07.02.2020

Рассмотрим этот код:

class Data{
public:
    Data() = default;
    Data(Data const&) = delete;
    Data(int) {
    }
};

class Data2{
public:
    Data2() = default;
    Data2(Data &) = delete;
    Data2(int) {
    }
};

int main()
{
    Data a {5};
    Data b  = 5;

    Data2 a2{5};
    Data2 b2  = 5;
}

До стандарта C ++ 17 неправильно сформирована только инициализация b. Две используемые здесь формы инициализации описаны ниже (копия из N4296):

15 Инициализация, которая происходит в форме = инициализатора скобок или равенства или условия (6.4), а также при передаче аргументов, возврате функции, генерации исключения (15.1), обработке исключения (15.3) и агрегировании инициализация члена (8.5.1) называется копией-инициализацией. [Примечание: инициализация копирования может вызвать перемещение (12.8). —В конце примечания]

16 Инициализация, которая происходит в формах

T x(a); 
T x{a}; 

а также в новых выражениях (5.3.4), выражениях static_cast (5.2.9), преобразованиях типов функциональной нотации (5.2.3), инициализаторах mem (12.6.2) и форме условия в фигурных скобках. называется прямой инициализацией.

потом

Если инициализация является прямой инициализацией или инициализацией копирования, когда cv-неквалифицированная версия исходного типа является тем же классом, что и класс назначения, или производным от него, рассматриваются конструкторы.

Это не наш случай, переходите к следующему абзацу

В противном случае (т. Е. Для остальных случаев копирования-инициализации) определяемые пользователем последовательности преобразования, которые могут преобразовывать из исходного типа в целевой тип или (когда используется функция преобразования) в его производный класс, перечисляются, как описано в 13.3. 1.4, а лучший выбирается по разрешению перегрузки (13.3). Если преобразование не может быть выполнено или неоднозначно, инициализация сформирована неправильно. Выбранная функция вызывается с выражением инициализатора в качестве аргумента; если функция является конструктором, вызов инициализирует временную версию cv-неквалифицированной версии целевого типа. Временное - это prvalue. Результат вызова (который является временным для случая конструктора) затем используется для прямой инициализации, в соответствии с приведенными выше правилами, объекта, который является адресатом инициализации копирования. В некоторых случаях реализации разрешается исключить копирование, присущее этой прямой инициализации, путем построения промежуточного результата непосредственно в инициализируемом объекте.

Таким образом, при условии, что константа 5 не относится к типу Data или Data2, тогда для b и b2 конструктор копирования был вызван во время инициализации копирования после преобразования 5 в правильный тип посредством прямой инициализации временного объекта, который может быть привязан к аргументу const Data&. конструктора, но не Data&, когда рассматриваются кандидаты в конструкторы.

У b был удален конструктор копирования, поэтому инициализация некорректна. b2 было запрещено инициализировать только неконстантный объект, вызов которого не может быть привязан к этому случаю. Исключение копирования не происходило в соответствии с правилами C ++ 11/14.

person Swift - Friday Pie    schedule 07.02.2020
comment
Но ни один из ваших случаев не применяется, поскольку OP инициализирует ссылку. - person xskxzr; 07.02.2020

При компиляции кода на C ++ 11 (вы использовали =default и =delete, таким образом, это как минимум C ++ 11) ошибка находится в строке # 1, другая (# 2) не имеет проблем:

$ g++ -Wall --std=c++11 -o toto toto.cpp
toto.cpp:14:8: error: copying variable of type 'Data' invokes deleted constructor
  Data d = a;  //#1
       ^   ~
toto.cpp:5:5: note: 'Data' has been explicitly marked deleted here
    Data(Data const&) = delete;
    ^
1 error generated.

Для №1 сначала создается [over.match.copy] с помощью [class.conv.ctor]. Таким образом он преобразуется в Data d = Data(a). Во-вторых, поскольку вы находитесь в области действия семантического компилятора перемещения, он не может найти правильный ctor, потому что:

11.4.4.2 Конструкторы копирования / перемещения

  1. [Примечание: когда конструктор перемещения не объявляется неявно или не предоставляется явно, выражения, которые в противном случае вызвали бы конструктор перемещения, могут вместо этого вызывать конструктор копирования. - конец примечания]

увы, copy-ctor был удален.

person Jean-Baptiste Yunès    schedule 20.01.2020
comment
здесь - это стандарт об инициализации копии ссылки. В противном случае создается временный объект типа T и инициализируется копией из объекта. Затем ссылка привязывается к этому временному объекту. Применяются правила инициализации копирования (явные конструкторы не рассматриваются). Это означает, что временный объект, связанный ссылкой, инициализируется в той же форме, что и #1 - person xmh0511; 20.01.2020
comment
Цитируемый документ не является стандартным (кажется, это локальная копия cppreference). Ответ от анаконгуа говорит вам то же самое. - person Jean-Baptiste Yunès; 20.01.2020
comment
если cppreference не является стандартным, должно быть это, я скопируйте раздел здесь - person xmh0511; 21.01.2020
comment
Если T1 или T2 является типом класса и T1 не связан со ссылкой на T2, пользовательские преобразования рассматриваются с использованием правил для инициализации копирования объекта типа «cv1 T1» пользователем: определенное преобразование ([dcl.init], [over.match.copy], [over.match.conv]); программа будет сформирована неправильно, если соответствующая не ссылочная инициализация копии будет неправильно сформирована. Результат вызова функции преобразования, как описано для инициализации копии без ссылки, затем используется для прямой инициализации ссылки. Для этой прямой инициализации определенные пользователем преобразования не рассматриваются. - person xmh0511; 21.01.2020
comment
Обратите внимание на выделенную жирным шрифтом часть. Это означает, что результат, связанный ссылкой, создается с использованием инициализации копирования - person xmh0511; 21.01.2020