конструктор c ++ и конструктор копирования - PullRequest
3 голосов
/ 11 апреля 2019

Я пытаюсь понять поведение следующего кода

/* code block 1 */
#include <iostream>
class A
{
        private:
            int value;

        public:
            A(int n) { std::cout << "int n " << std::endl; value = n; }
            A(const A &other) { std::cout << " other " << std::endl; value = other.value; }
            // A (A &&other) { std::cout  << "other rvalue" << std::endl; value = other.value; }

            void print(){ std::cout << "print " << value << std::endl; }

};

int main(int argc, char **argv)
{
        A a = 10;
        A b = a;
        b.print();
        return 0;
}

Когда я компилирую код выше, он работает как я ожидаю

/* code block 2 */
g++ -std=c++11 t.cpp 
./a.out
int n 
 other 
print 10

Когда я удаляю const из конструктора копирования

/* code block 3 */
class A
{
        ...
        A(int n) { std::cout << "int n " << std::endl; value = n; }
        A(A &other) { std::cout << " other " << std::endl; value = other.value; }
        // A (A &&other) { std::cout  << "other rvalue" << std::endl; value = other.value; }
}

Компилятор не будет компилироваться

/* code block 4 */
t.cpp:19:5: error: no viable constructor copying variable of type 'A'
            A a = 10;
              ^   ~~
t.cpp:9:4: note: candidate constructor not viable: no known conversion from 'A' to 'int' for 1st argument
                    A(int n) { std::cout << "int n " << std::endl; value = n; }
                    ^
t.cpp:10:4: note: candidate constructor not viable: expects an l-value for 1st argument
                    A(A &other) { std::cout << " other " << std::endl; value = other.value; }

из результата t.cpp: 9: 4 , кажется, что компилятор пытается преобразовать A в int, но код будет A a = 10; , если я компилятор, я буду либо

  1. пытается инициализировать временную переменную с типом A из целого числа 10 , а затем использовать конструктор копирования A (A и другие) для инициализации a

  2. инициализация a с функцией конструктора A (int) напрямую

Я запутался в выводе компилятора из t.cpp: 9: 4

из выходных данных t.cpp: 10: 4 , компилятор говорит, что ожидает конструктор копирования с l-значением, поэтому я изменяю код на

/* code block 5 */
class A
{
        ...
        A(int n) { std::cout << "int n " << std::endl; value = n; }
        A(A &other) { std::cout << " other " << std::endl; value = other.value; }
        A (A &&other) { std::cout  << "other rvalue" << std::endl; value = other.value; }
}

Когда я следую подсказке, чтобы определить конструктор копирования rvalue, вывод показывает, что конструктор копирования rvalue не был вызван

/* code block 6 */
g++ -std=c++11 t.cpp 
int n 
 other 
print 10

Вопросы:

  1. (в блоке кода 3), почему я не могу удалить const из конструктора копирования?
  2. (в блоке кода 4 -> t.cpp: 9: 4), почему компилятор пытается преобразовать из 'A' в 'int'?
  3. (в блоке кода 5) компилятор говорит, что ему нужен конструктор копирования rvalue (из блока кода 4 -> t.cpp: 10: 4), поэтому я определяю его, но в текущем выводе показан конструктор копирования rvalue. не звонил, почему?

Ответы [ 3 ]

5 голосов
/ 11 апреля 2019

То, что вы видите, называется copy elision в компиляторе до C ++ 17 (попробуйте это с C ++ 17 на проводнике компилятора или wandbox с -std=c++17 против -std=c++14 флагов). Начиная с C ++ 17, компилятор обязан исключать множество случаев копирования и перемещения конструкторов и создавать объекты напрямую без каких-либо промежуточных объектов.

В отличие от

A a { 10 };

линия

A a = 10;

означает, что сначала создается временный объект, как если бы код имел:

A a = A(10);

До C ++ 17 компилятор позволял оптимизировать этот код и создавать a непосредственно из 10 без временного объекта. Обратите внимание, что акцент делается на том, что разрешено , но не требуется для выполнения этой copy elision оптимизации. Вы наблюдали эту разрешенную оптимизацию.

Компилятор должен был скомпилировать или завершить работу кода независимо от своего решения сделать копию. Если компилятор не мог вызвать конструктор копирования, как в вашем случае, то он должен был безоговорочно завершить компиляцию, даже если он решил сделать исключение копирования. Это изменилось в C ++ 17, и компилятору теперь требуется , чтобы в этом случае оптимизировать копирование. Поскольку гарантированно исключается конструктор копирования, конструктор копирования даже не требуется, и код может компилироваться без ошибки.

Обратите внимание на конструктор копирования без const:

A(A &other) { std::cout << " other " << std::endl; value = other.value; }

Без возможности копирования этот конструктор копирования нельзя использовать для:

A a = A(10);

Его нельзя использовать, потому что A (10) является временным объектом, и как таковой может быть передан в качестве параметра rvalue конструкторам и методам, таким как

A(A && other);
foo(A && other);

или передается в качестве константного ссылочного параметра для конструкторов и методов, подобных

A(const A& other);
bar(const A& other);

Но его нельзя передать как обычный изменяемый параметр (как в вашем кодовом блоке 3).

С elision copy в этих случаях он даже не пытается вызвать копию или конструктор перемещения.

Все еще нужно вызвать конструктор копирования для

A b = a;

и он может сделать это с изменяемым параметром, только потому, что a не является ни временным, ни постоянным объектом. Если вы сделаете a const, то код не будет скомпилирован, когда конструктор копирования не получит const (для C ++ 17 и более ранних версий):

const A a = 10;
A b = a;
//  ^^  this will fail

Замечание: следующая строка гарантированно не вызовет конструктор копирования ни разу с C ++ 17:

A a = A(A(A(A(1))));
1 голос
/ 11 апреля 2019

Когда вы пишете

A a = 10;

Компилятор преобразует 10 во временный объект, а затем вызывает конструктор копирования для создания.

A a = A(10);

Рассмотрим эту программу,

#include <iostream>
class A
{
        private:
            int value;

        public:
            A(int n) { std::cout << "int n " << std::endl; value = n; }
            A(const A &other) { std::cout << " other " << std::endl; value = other.value; }
            //A (A &&other) { std::cout  << "other lvalue" << std::endl; value = other.value; }

            void print(){ std::cout << "print " << value << std::endl; }

};

int main(int argc, char **argv)
{
        A a = 10;
        //A a(1);
        //A b = a;
        //b.print();
        return 0;
}

и скомпилируйте его с

g++ t.cpp -std=c++11

При запуске программы ее вывод будет

int n 

Теперь вы можете задаться вопросом, почему конструктор копирования A(const A &other) не вызывается. Это из-за copy elision в C ++. Компилятор может оптимизировать вызов конструктора копирования и напрямую вызвать соответствующий конструктор. Поэтому вместо A a = A(10); то, что называется, это A a(10);

Если вы хотите отключить копирование elision, скомпилируйте вышеуказанную программу с помощью

g++ t.cpp -std=c++11 -fno-elide-constructors

Теперь, запустив программу, вы можете увидеть ниже вывод

int n 
 other 

Эмиссии нет. Итак, A a = A(10); вызывается. Сначала создается временный объект, а затем вызывается конструктор копирования для создания a.

(in code block 3) why can't I remove the const from copy constructor?

Потому что временные объекты не могут быть привязаны к lvalue-ссылке. Они могут быть привязаны только к rvalue-ссылке или const lvalue-ссылке. A(10) создает временный объект, который может быть привязан только к ссылке const lvalue (const A &) или к ссылке rvalue (A &&).

(in code block 5) the compiler says that it need a rvalue copy constructor(from code block 4 -> t.cpp:10:4), so I define one, but the running output show the rvalue copy constructor wasn't called, why?

Это происходит из-за исключения копирования. Скомпилируйте его с помощью -fno-elide-constructors, и затем вы увидите вызов конструктора rvalue. Смотри ниже.

#include <iostream>
class A
{
        private:
            int value;

        public:
            A(int n) { std::cout << "int n " << std::endl; value = n; }
            A(A &other) { std::cout << " other " << std::endl; value = other.value; }
            A (A &&other) { std::cout  << "other lvalue" << std::endl; value = other.value; }

            void print(){ std::cout << "print " << value << std::endl; }

};

int main(int argc, char **argv)
{
        A a = 10;
        //A a(1);
        //A b = a;
        //b.print();
        return 0;
}

Компиляция:

g++ t.cpp -std=c++11 -fno-elide-constructors

выход

int n 
other lvalue
1 голос
/ 11 апреля 2019

Когда ваш код выполняется

A a = 10;

Он преобразует 10 в переменную типа A и вызывает конструктор копирования для инициализации 'a'.Поскольку 10 не имеет ссылки в памяти, это значение.И так как ваш конструктор копирования принимает «другое» по ссылке, вы не можете передать значение r, так как нет ссылки, которую нужно изменить, если она будет изменена.C ++ позволяет передавать значения r по ссылке, только если это по константной ссылке.

Причина, по которой он пытается привести его к типу int, заключается в том, что у вас есть только два конструктора: один принимает значение типа int, а другой - объект типа «A» по ссылке.Поскольку он уже приводил 10 к объекту типа 'A', и этот объект является значением r, он просто заявляет, что ни один конструктор не может принять этот объект.

Как примечание: это напоминание, чтобы всегда передаватьпо постоянной ссылке, когда объект не изменяется.

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