Конструктор преобразования против оператора преобразования: приоритет
Чтение некоторых вопросов здесь о SO относительно операторов преобразования и конструкторов заставило меня задуматься о взаимодействии между ними, а именно, когда происходит "неоднозначный" вызов. Рассмотрим следующий код:
class A;
class B {
public:
B(){}
B(const A&) //conversion constructor
{
cout << "called B's conversion constructor" << endl;
}
};
class A {
public:
operator B() //conversion operator
{
cout << "called A's conversion operator" << endl;
return B();
}
};
int main()
{
B b = A(); //what should be called here? apparently, A::operator B()
return 0;
}
Приведенный выше код отображает "оператор преобразования вызываемого А", что означает, что оператор преобразования вызывается в отличие от конструктора. Если вы удалите / закомментируете operator B()
код из A
компилятор с радостью переключится на использование конструктора (без других изменений в коде).
Мои вопросы:
- Поскольку компилятор не учитывает
B b = A();
чтобы быть неоднозначным вызовом, здесь должен быть какой-то приоритет. Где именно этот приоритет установлен? (ссылка / цитата из стандарта C++ приветствуется) - С объектно-ориентированной философской точки зрения должен ли код вести себя? Кто знает больше о том, как
A
объект должен статьB
объект,A
или жеB
? Согласно C++, ответA
- есть ли что-то в объектно-ориентированной практике, которая предполагает, что это должно быть так? Лично для меня это будет иметь смысл в любом случае, поэтому мне интересно узнать, как был сделан выбор.
заранее спасибо
2 ответа
Вы выполняете копирование инициализации, и функции-кандидаты, которые, как считается, выполняют преобразования в последовательности преобразования, являются функциями преобразования и конструкторами преобразования. Это в вашем случае
B(const A&)
operator B()
Вот так вы их объявляете. Разрешение перегрузки абстрагируется от этого и преобразует каждого кандидата в список параметров, соответствующих аргументам вызова. Параметры
B(const A&)
B(A&)
Второй - потому что функция преобразования является функцией-членом. A&
это так называемый неявный объектный параметр, который генерируется, когда кандидат является функцией-членом. Теперь аргумент имеет тип A
, При связывании неявного параметра объекта неконстантная ссылка может привязываться к значению r. Итак, другое правило гласит, что если у вас есть две жизнеспособные функции, чьи параметры являются ссылками, тогда победит кандидат с наименьшей квалификацией const. Вот почему ваша функция преобразования выигрывает. Попробуйте сделать operator B
постоянная функция-член. Вы заметите двусмысленность.
С объектно-ориентированной философской точки зрения должен ли код вести себя? Кто знает больше о том, как объект A должен стать объектом B, A или B? Согласно C++ ответ A - есть ли что-то в объектно-ориентированной практике, которая предполагает, что это должно быть так? Лично для меня это будет иметь смысл в любом случае, поэтому мне интересно узнать, как был сделан выбор.
Для записи, если вы сделаете функцию преобразования константной функцией-членом, то GCC выберет конструктор (поэтому GCC, кажется, считает, что B
имеет больше дела с этим?). Переключиться в педантичный режим (-pedantic
) чтобы это стало причиной диагностики.
Standardese, 8.5/14
В противном случае (т. Е. Для остальных случаев инициализации копирования) определяемые пользователем последовательности преобразования, которые могут преобразовывать из типа источника в тип назначения или (когда используется функция преобразования) в его производный класс, перечисляются, как описано в 13.3.1.4, а лучший выбирается с помощью разрешения перегрузки (13.3).
А также 13.3.1.4
Разрешение перегрузки используется для выбора определяемого пользователем преобразования, которое будет вызвано. Предполагая, что "cv1 T" является типом инициализируемого объекта, а T - тип класса, функции-кандидаты выбираются следующим образом:
- Конвертирующие конструкторы (12.3.1) из T являются функциями-кандидатами.
- Когда типом выражения инициализатора является тип класса "cv S", рассматриваются функции преобразования S и его базовых классов. Те, которые не скрыты в S и дают тип, чья cv-неквалифицированная версия имеет тот же тип, что и T, или является ее производным классом, являются функциями-кандидатами. Функции преобразования, которые возвращают "ссылку на X", возвращают l-значения типа X, и поэтому считается, что они дают X для этого процесса выбора функций-кандидатов.
В обоих случаях список аргументов имеет один аргумент, который является выражением инициализатора. [Примечание: этот аргумент будет сравниваться с первым параметром конструкторов и с неявным параметром объекта функций преобразования. ]
А также 13.3.3.2/3
- Стандартная последовательность преобразования S1 является лучшей последовательностью преобразования, чем стандартная последовательность преобразования S2, если [...] S1 и S2 являются ссылочными привязками (8.5.3), и типы, на которые ссылаются ссылки, являются одним и тем же типом, за исключением высшего уровня cv-qualifiers, и тип, к которому относится ссылка, инициализированная с помощью S2, является более квалифицированным по cv, чем тип, к которому относится ссылка, инициализированная с помощью S1.
Кажется, у MSVS2008 есть свое мнение о выборе конструктора: он вызывает конструктор копирования в B независимо от константности оператора A. Так что будьте осторожны, даже если стандарт определяет правильное поведение.
Я думал, что MSVS просто ищет подходящий конструктор перед оператором преобразования, но потом обнаружил, что он начинает вызывать оператор A B(), если вы удалите слово const из конструктора B. Вероятно, это имеет какое-то особое поведение для временных, потому что следующий код все еще вызывает конструктор B:
A a;
B b = a;