Код:
#include <iostream>
#include <cstring>
class MyString
{
public:
MyString() {
std::cout << "1";
}
explicit MyString(const char *str) {
std::cout << "2";
}
};
int main()
{
MyString str1 = MyString("abcde");
MyString str2("abcde");
MyString str3 = "abcde";
return 0;
}
str1 и str2 создаются нормально, распечатываются 2. Но когда я запускаю его с помощью str3, я получаю следующее сообщение об ошибке:
Compilation failed due to following error(s).main.cpp: In function ‘int main()’:
main.cpp:19:19: error: conversion from ‘const char [6]’ to non-scalar type ‘MyString’ requested
19 | MyString str3 = "abcde";
Разве все три строки не должны вызывать второй конструктор из-за RVO? Или это потому, что я использую компилятор, отличный от RVO? Я попробовал это как на CMake, так и на gcc, и оба получили одно и то же сообщение об ошибке.
Я попытался создать operator= и все равно получил ту же ошибку, так что это явно не от RVO:
#include <iostream>
#include <cstring>
class MyString
{
public:
MyString() {
std::cout << "1";
}
explicit MyString(const char *str) {
std::cout << "2";
}
MyString& operator=(const char *str) {
std::cout << "3";
return *this;
}
};
int main()
{
MyString str1 = MyString("abcde");
MyString str2("abcde");
MyString str3 = "abcde";
return 0;
}
Я довольно долго искал, но не смог найти ответа.
Такие оптимизации, как RVO, никогда не играют роли в принятии решения о том, корректна ли программа или нет, и оператор присваивания здесь совершенно не важен.
Кроме того, несмотря на использование символа =, здесь нет присваивания, только инициализация. Поэтому оператор присваивания не рассматривается.
На YouTube есть пара очень хороших видеороликов, объясняющих кошмар инициализации на C++. Мой любимый — C++ on Sea 2019 – Тимур Думлер – «Инициализация в современном C++» , но другие почти так же хороши: CppCon 2018 – Николай Йосуттис – «Кошмар инициализации в C++».
@WeijunZhou, насколько я знаю, RVO используется для оптимизации таких вещей, как вместо вызова конструктора и создания объекта, а затем копирования его с помощью op=, он просто вызывает конструктор и создает объект do, без какого-либо копирования при написании таких вещей как 'A a = A(5)'. Я подумал, что в конкретном компиляторе нет RVO, поэтому попробовал его просто для проверки, но, как я уже писал, проблема была не в этом. Но я думаю, что это всего лишь один случай компилятора, который, как я понял, во многом зависит от компилятора.
@tbxfreeware Я проверю их, поскольку сейчас изучаю эту тему и знаю, что большинство вещей зависят от компилятора, но большое спасибо за это!
@BoP, спасибо, это решает проблему! Я рассмотрю подробнее явный вариант, поскольку знал только, что его можно использовать, поэтому нельзя вызывать функции, которые принимают объект с чем-то из однопараметрического конструктора, но я определенно не знал, что это также один из их вариантов использования. (или, я думаю, случаи антииспользования). Большое спасибо!
Я имел в виду, что оптимизация применяется на более позднем этапе и представляет собой детали реализации. Даже если компилятор решит пропустить вызов конструктора в рамках оптимизации, сам вызов конструктора все равно должен быть правильно сформирован, поэтому вам вообще не следует рассматривать оптимизацию, если вы хотите узнать, правильно ли сформирована какая-либо программа на C++. . И нет, РВО не имеет отношения к operator=. Возможно, вы думаете о конструкторе копирования. Возможно, это как-то связано с RVO. Но оператор присваивания никогда не имеет значения. A a=A(5) не вызывает оператор присваивания, даже если есть =.
«копирование с помощью op = ", похоже, вас запутало. copying и op= на самом деле являются взаимоисключающими. Если вы копируете объект, op= не имеет значения. op= актуален только в том случае, если у вас уже есть полный объект и вы его перезаписываете, чего не происходит в RVO. В частности, A a=f() не создает A, а затем вызывает op= с помощью f(). Он копирует a из f(), что включает в себя конструктор копирования, а не op=, и этот вызов конструктора копирования может быть опущен RVO.





MyString str3 = "abcde"; выполняет инициализацию копирования, которая не работает с явными конструкторами. Более формально:
[...] если
Tявляется типом класса, а cv-неквалифицированная версия типа другого не являетсяTи не является производной отT, или еслиTне является типом класса, но типotherявляется типом класса, определяемые пользователем последовательности преобразования, которые могут конвертировать из типа другого вT(или в тип, производный отT, еслиTявляется типом класса и доступна функция преобразования), проверяются, и лучшая из них выбирается посредством разрешения перегрузки.
Здесь T — это тип класса (MyString), а other не принадлежит к типу класса (const char[6]), а определяемая пользователем последовательность преобразований отсутствует, поскольку она не может содержать конструкторы explicit.
Конструктор явный поэтому не будет использоваться для неявного преобразования.