@Vrnv

Как правильно перегрузить конструкторы в классе?

Столкнулся с проблемой: При такой записи, как ниже, все работает и все прекрасно компилируется:
#include <iostream>
using namespace std;


class A{
    private: 
        int aa;
        int ab;
    public:
        A():aa(0),ab(0){}
        A(int a):aa(a),ab(0){}
        void Show(){
            cout<<"Var1: "<<aa<<endl<<"Var2: "<<ab<<endl;
        }
};


int main(){
    A obj = 5;
    obj.Show();



    return 0;
}

Но если я добавляю конструктор копирования, то у меня вылетает ошибка:
cannot bind non-const lvalue reference of type 'A&' to an rvalue of type 'A'
     A obj = 5;


#include <iostream>
using namespace std;


class A{
    private: 
        int aa;
        int ab;
    public:
        A():aa(0),ab(0){}
        A(int a):aa(a),ab(0){}
        A(A& obj);
        void Show(){
            cout<<"Var1: "<<aa<<endl<<"Var2: "<<ab<<endl;
        }
};


int main(){
    A obj = 5;
    obj.Show();



    return 0;
}

Подскажите: Как правильно перегрузить конструкторы так, чтобы не было этого конфликта?
  • Вопрос задан
  • 627 просмотров
Решения вопроса 1
@majstar_Zubr
C++, C#, gamedev
Рассмотрим проблему подробнее.

class A {
private:
 int aa;
 int ab;

public:
 A()
     : aa(0), ab(0) {}  // (4) note: candidate constructor not viable: requires
                        // 0 arguments, but 1 was provided
 A(int a)
     : aa(a), ab(0) {}  // (2) note: candidate constructor not viable: no known
                        // conversion from 'A' to 'int' for 1st argument
 A(A& obj);  // (3) note: andidate constructor not viable: expects an l-value
             // for 1st argument
 void Show() { cout << "Var1: " << aa << endl << "Var2: " << ab << endl; }
};

int main() {
 A obj = 5;  // (1) error: cannot bind non-const lvalue reference of type ‘A&’
             // to an rvalue of type ‘A’
 obj.Show();

 return 0;
}


Стандарт обязывает понимать

A(A& obj);

как user-defined конструктор копирования. Поэтому, конструктор копирования T::T(const T&) не будет объявлен по-умолчанию компилятором. Со стандарта C++11 предоставляется возможность заставить компилятор генерировать неявно-объявленный конструктор копирования ключевым словом default

A(A& obj) = default;

Неявно объявленный конструктор копирования по-умолчанию имеет сигнатуру T::T(const T&) но лишь в случае, когда все базовые классы T имеют конструкторы копирования с параметрами const B& или const volatile B&, и когда все не статические данные-члены класса T имеют конструкторы копирования с параметрами const M& или const volatile M&.

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

T::T(T&)

В данном случае, конструктор копирования A(A& obj) является тривиальным с сигнатурой сгенерированного неявно-определённого конструктора копирования T::T(T&).
Тривиальное копирование практически аналогично std::memmove

В строке ошибки компиляции происходит следующее:

1) оператор = в строке с ошибкой компиляции не является инициализирующим, поскольку литерал 5 является (rvalue, нельзя взять адрес) const int. Для исполнения операции присваивания компилятор сначала конструирует A(5);

...
A(int a) : aa(a), ab(0) {
 cout << "Copy ctor with 1 parameter is called " << endl;
}
…
int main() {
…


Вывод:
Copy ctor with 1 parameter is called
Var1: 5
Var2: 0


2) операция присваивания имеет дело с A obj = A (5):

Справа от оператора присваивания находится временный rvalue типа class A.
Данное присваивание является инициализирующим, что делает его эквивалентным A obj(A(5));
Для данной операции необходим конструктор с сигнатурой T::T(T&&)
Это - конструктор перемещения, и он в классе A отсутствует, поскольку неявное определение конструктора перемещения в классе требует отсутствия user-defined конструкторов копирования, оператора = копирования, оператора = присваивания, деструктора. В нашем случае, у нас имеется user-defined A(A& obj);

Учитывая вышесказанное, для исправления ошибки компиляции можно либо удалить строку
A(A& obj); ,
что приведёт к неявному определению компилятором тривиальных конструкторов копирования и перемещения, либо добавить ещё в объявление класса A строку
A(A&& obj);

С точки зрения стандарта С++11 и выше, можно утверждать, что выражения A(A& obj); и A(A&& obj); соответственно эквивалентны A(A& obj) = default; и A(A&& obj) = default;
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы