Инициализация копирования с удаленным конструктором копирования в инициализации ссылки - PullRequest
19 голосов
/ 20 января 2020

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

#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, пользовательские преобразования рассматриваются с использованием правил copy-initialization объекта типа «cv1 T1» путем пользовательского преобразования ([dcl.init], [over. match.copy], [over.match.conv]); программа некорректна, если соответствующая нереферентная инициализация копирования будет некорректной. Результат вызова функции преобразования, как описано для инициализации копирования без ссылки, затем используется для прямой инициализации ссылки. Для этой прямой инициализации пользовательские преобразования не рассматриваются

Копирование инициализации

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

В соответствии со стандартом тип a равен int, а тип инициализированной ссылки - Data, поэтому от int до Data, пользовательские преобразования рассматриваются с использованием правил copy-initialization объекта типа «cv1 T1» путем пользовательского преобразования . Это означает, что Data const& d_rf = a; можно перевести на Data temporary = a; Data const& d_rf = temporary;. Для Data temporary = a;, хотя copy elision существует, конструктор копирования / перемещения должен быть проверен, доступен ли он , но конструктор копирования из 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 = "ab c"; // rs ссылается на временное инициализированное копированием из массива символов]

ОБНОВЛЕНИЕ:

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

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

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

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

Вышеуказанные правила здесь:

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

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

Ответы [ 6 ]

4 голосов
/ 02 февраля 2020

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

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

4 голосов
/ 23 января 2020

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

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

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

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

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

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

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

2 голосов
/ 05 февраля 2020

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

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

РЕДАКТИРОВАТЬ =================== ============

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

Best, FM.

0 голосов
/ 07 февраля 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), как и при передаче аргумента, функция return, инициализация исключения (15.1), обработка исключения (15.3) и инициализация агрегатного члена (8.5.1), называется copy-initialization. [Примечание: инициализация копирования может вызвать движение (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-unqualified версии назначения. Временное является prvalue. Результат вызова (который является временным для случая конструктора) затем используется для прямой инициализации, в соответствии с приведенными выше правилами, объекта, который является местом назначения инициализации копирования. В некоторых случаях реализация позволяет исключить копирование, присущее этой прямой инициализации, путем создания промежуточного результата непосредственно в инициализируемый объект

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

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

0 голосов
/ 07 февраля 2020

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

0 голосов
/ 20 января 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). Во-вторых, поскольку вы находитесь в области перемещения, компилятор semanti c не может найти правильный ctor, потому что:

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

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

увы, копия-ctor была удалена.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...