2

В названии говорится все. Однако, пожалуйста, возьмите string в качестве заполнителя для любого класса.Есть ли разница между `string s (" hello ");` и `string s =" hello ";`

std::string s1("hello"); // construct from arguments 
std::string s2 = "hello"; // ??? 
std::string s3;   // construct with default values 
s3 = "hello";    // assign 

Интересно, если заявление о s2 делает то же самое, как для s1 или s3.

+1

Возможный дубликат [Есть ли разница в C++ между инициализацией копирования и прямой инициализацией?] (Http://stackoverflow.com/questions/1051379/is-there-a-difference-in-c-between-copy- initialization-and-direct-initializati) –

+0

Также: [Инициализация и присвоение] (http://stackoverflow.com/questions/7350155/initialisation-and-assignment) –

ответ

14

Корпус s2: copy initialization. Это инициализация, а не назначение как случай s3.

Обратите внимание, что для std::string, эффект одинаков для s1 и s2, в apporiate конструктора (т.е. std::string::string(const char*)) будет вызываться для создания объекта. Но существует разница между комбинацией копий и direct initialization (случай s1); для инициализации копирования явный конструктор не рассматривается. Предположим, что std::string::string(const char*) объявлен explicit, что означает, что неявное преобразование от const char* до std::string не допускается; то второй случай не будет компилироваться снова.

Копирование-инициализация менее разрешительная, чем прямая инициализация: явные конструкторы не преобразуют конструкторы и не рассматриваются для инициализации копирования.

2

В этом случае s1 и s2 сделать точно то же самое: они оба называют конструктор для const char*. (Некоторые люди предпочитают использовать = для ясности).

Для s3 вызывается конструктор по умолчанию, а затем operator= - const char*.

1

Если класс не имеет доступный конструктор копирования, то вторая форма инициализации является недействительным:

[temp]$ cat test.cpp 
struct S { 
    S(int); 
private: 
    S(const S&); 
}; 

S s1(3); // okay 
S s2 = 3; // invalid 
[temp]$ clang++ -std=gnu++1z -c test.cpp 
test.cpp:8:3: error: calling a private constructor of class 'S' 
S s2 = 3; // invalid 
^
test.cpp:4:5: note: declared private here 
    S(const S&); 
    ^
1 error generated. 
[temp]$ 

Разница заключается в том, что второй один, формально, создает временный объект типа S, инициализируется значением 3, а затем копирует это временное значение в s2. Компилятору разрешено пропустить копию и построить s2 напрямую, но только если копия была бы действительна.

+0

Я думаю, что копия ctor здесь неактуальна. http://melpon.org/wandbox/permlink/2VI5MmtM8qG73qTs – songyuanyao

+0

@songyuanyao - я отредактировал свой ответ, чтобы показать выходные данные компилятора и переключатели (которые соответствуют вашим). Это все еще ошибка. –

+0

Возможно, это проблема с C++ 17. Из [здесь] (http://en.cppreference.com/w/cpp/language/copy_initialization): «Последний шаг обычно оптимизируется, и результат преобразования создается непосредственно в памяти, выделенной для целевого объекта, но соответствующий конструктор (перемещение или копирование) должен быть доступен, даже если он не используется (до C++ 17) ». [C++ 14] (http://melpon.org/wandbox/permlink/9YhwdZPPV8vIYHN0) vs [C++ 17] (http://melpon.org/wandbox/permlink/VbNcgcySstUZY3RZ) – songyuanyao

2

Хотя конечный результат всех трех методов один и тот же (строка будет назначена переменной), существуют определенные фундаментальные различия, которые идут глубже, чем синтаксис. Я рассмотрю все 3 сценария, на которые ссылаются ваши 3 строки:

Первый случай: s1 - пример прямой инициализации. Прямая инициализация охватывает ряд разностных сценариев, которые определяются следующим образом:

Инициализация с помощью непустого списка выражений в скобках.

Здесь s1 не имеет типа данных класса, но тип std::string данных, поэтому стандартное преобразование будет иметь место, чтобы преобразовать тип данных в круглых скобках в сорте-неквалифицированные версии s1, который const *char.Cv-unqualified означает, что нет такого классификатора, как (const) или (изменчивый) прилагается к переменной. Обратите внимание, что в случае прямой инициализации это намного более разрешительная, чем копирование-инициализация, которая является предметом s2. Это связано с тем, что инициализация копирования относится только к конструкторам и функциям преобразования, определенным пользователем, которые являются non_explicit (т. Е. Неявным). С другой стороны, прямая инициализация рассматривает неявные и явные конструкторы и пользовательские функции преобразования.

При переходе вторая строка s2 является примером инициализации копии. Проще говоря, он копирует значение с левой стороны вправо. Это пример:

, когда именованная переменная (автоматическая, статическая или поточно-локальная) не ссылочного типа T объявляется с инициализатором, состоящим из знака равенства, за которым следует выражение.

Процесс, охватываемый этим методом, тот же. Поскольку s2 не имеет тип данных класса, а тип данных std::string, он будет использовать стандартное преобразование для преобразования значения строки с правой стороны в значение типа const *char слева. Однако, если функция объявлена ​​явно, стандартное преобразование не может быть выполнено в отличие от инициализатора копии, и компиляция кода завершится с ошибкой.

См. Некоторые примеры кода по сравнению с двумя типами инициализаций. Это должно очистить любые неурядицы сверху:

struct Exp { explicit Exp(const char*) {} }; // This function has an explicit constructor; therefore, we cannot use a copy initialization here 
    Exp e1("abc"); // Direct initialization is valid here 
    Exp e2 = "abc"; // Error, copy-initialization does not consider explicit constructor 
      
    struct Imp { Imp(const char*) {} }; // Here we have an implicit constructor; therefore, a copy initializer can be used 
    Imp i1("abc"); // Direct initialization always works 
    Imp i2 = "abc"; // Copy initialization works here due to implicit copy constructor 

Переходя к третьему случаю, это даже не случай инициализации, это случай уступки. Как вы сказали в своих комментариях, переменная s3 инициализируется строкой по умолчанию. Эта строка заменяется на «Hello», когда вы используете знак равенства. Здесь происходит то, что когда s3 объявляется в string s3;, вызывается конструктор по умолчанию для std::string и задается строковое значение по умолчанию. Эта строка по умолчанию заменяется на привет в следующей строке, когда вы используете знак =.

Если мы смотрим на более эффективные с точки зрения скорости при работе, разница является предельной. Тем не менее, s1 занимает лучшее время, чтобы работать, если мы только делаем это:

int main(void) 
    { 
     string a("Hello"); 
    } 

Это произошло следующее время и память для компиляции и запуска:

время компиляции: 0,32 сек, абсолютное время работы: 0,14 сек, процессорное время: 0 сек, пиковая память: 3 Мб, абсолютное время службы: 0,46 сек

Если мы посмотрим на строки s2 кодированной следующим образом:

int main(void) 
    { 
     string a = "Hello"; 
    } 

Тогда общее время, необходимое для работы программы является: время

Компиляции: 0,32 сек, абсолютное время работы: 0,14 сек, процессорное время: 0 сек, пиковая память: 3 Мб, время абсолютного обслуживания : 0,47 с

Время выполнения, использующее инициализатор копии, принимает 0.01 секунд для запуска, чем прямой инициализатор. Разница существует, но маргинальная.

3-й случай с s3, если кодируются следующим образом:

int main(void) 
    { 
     string a; 
     a = "Hello"; 
    } 

имеет в общей сложности работает, пространство и время компиляции взят из:

Сборника Время: 0,32 сек, абсолютное время работы : 0,14 с, время процессора: 0 с, пик памяти: 3 Мб, абсолютное время обслуживания: 0,47 с

Я хотел бы указать что-то здесь: разница во времени между второй и третий методы, скорее всего, NOT ноль; скорее, это разница во времени менее 0,01 секунды, при этом третий метод занимает больше времени (s3). Это потому, что у него есть 2 строки кода для работы; один - объявление переменной, а другое - назначение строки переменной.

Надеюсь, это ответит на ваш вопрос.

+0

Большое спасибо за подробный ответ. Для ваших тестов производительности вы должны повторять один и тот же шаг в цикле много раз и вычислять среднее время. Я считаю, что это делает разницу в производительности намного яснее. Кроме того, вы должны убедиться, что компилятор не просто оптимизирует неиспользуемую строковую переменную! – Fabian

+0

На самом деле @Fabian, я запускал их несколько раз, но так как время точно до двух знаков после запятой, с сайта, который я использовал, время было __exactly__ одинаково! То, что мне было нужно, было более точное время, чтобы найти среднее. Спасибо за ваши отзывы, тем не менее :-) –