Путаница конструктора - PullRequest
8 голосов
/ 28 мая 2011

Мне всегда кажется, что я хорошо знаю C ++, но иногда меня удивляют даже самые фундаментальные вещи.

В следующем сценарии я не понимаю, почему вызывается конструктор Derived::Derived(const Base&):

class Base
{ };

class Derived : public Base
{
    public:

    Derived() { }

    Derived(const Base& b) 
    {
        std::cout << "Called Derived::Derived(const Base& b)" << std::endl;
    }
};

int main()
{
    Derived d;
    Base b;
    d = b;
}

Это выводит: Called Derived::Derived(const Base& b), указывая, что был вызван второй конструктор в Derived.Теперь я думал, что хорошо знаю C ++, но не могу понять, почему будет вызываться этот конструктор.Я понимаю всю концепцию «правила четырех» и думаю, что выражение d = b будет выполнять одно из двух: либо 1) вызовет неявный (генерируемый компилятором) оператор присваивания Base, либо 2Вызвать ошибку компилятора, сообщив, что функция Derived& operator = (const Base&) не существует.

Вместо этого он вызывал конструктор , хотя выражение d = b является выражением присваивания.

Так почему это происходит?

Ответы [ 5 ]

11 голосов
/ 28 мая 2011

d = b может произойти, потому что b конвертируется в Derived.Второй конструктор используется для автоматического преобразования типов.Это как d = (производная) b

Производная isa Base, но Base не является Derived, поэтому ее необходимо преобразовать перед назначением.

6 голосов
/ 28 мая 2011

присвоение базы производным? возможно, вы имели в виду (а) под ссылкой (б) или выведены из базы. Это на самом деле не имеет смысла, но компилятор правильно использует ваш (неявный) конструктор для преобразования экземпляра Base в новый производный экземпляр (который впоследствии назначается в d).

Используйте конструктор explicut, чтобы предотвратить это автоматически.

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

5 голосов
/ 28 мая 2011

Здесь представлены две взаимодействующие функции:

  • Операторы присваивания никогда не наследуются
  • Неявный конструктор или оператор преобразования (operator T()) определяют пользовательское преобразование, которое можно неявно использовать как часть последовательности преобразования

Операторы присваивания никогда не наследуются

Простой пример кода:

struct Base {}; // implicitly declares operator=(Base const&);
struct Derived: Base {}; // implicitly declares operator=(Derived const&);

int main() {
  Derived d;
  Base b;
  d = b; // fails
}

с ideone :

prog.cpp: In function ‘int main()’:
prog.cpp:7: error: no match for ‘operator=’ in ‘d = b’
prog.cpp:2: note: candidates are: Derived& Derived::operator=(const Derived&)

Последовательность преобразования

Всякий раз, когда есть несоответствие "импеданса", например, здесь:

  • Derived::operator= ожидает Derived const& аргумент
  • a Base& предоставляется

компилятор попытается установить последовательность преобразования для преодоления разрыва. Такая последовательность преобразования может содержать в большинстве одно пользовательское преобразование.

Здесь он будет искать:

  • любой конструктор Derived, который можно вызвать с помощью Base& (не явно)
  • оператор преобразования в Base, который даст Derived item

Нет Base::operator Derived(), но есть конструктор Derived::Derived(Base const&).

Поэтому наша последовательность конвертации определена для нас:

  • Base&
  • Base const& (тривиально)
  • Derived (с использованием Derived::Derived(Base const&))
  • Derived const& (временный объект привязан к константной ссылке)

А потом Derived::operator(Derived const&) вызывается.

В действии

Если мы дополним код еще несколькими трассами, мы сможем увидеть его в действии .

#include <iostream>

struct Base {}; // implicitly declares Base& operator(Base const&);
struct Derived: Base {
  Derived() {}
  Derived(Base const&) { std::cout << "Derived::Derived(Base const&)\n"; }
  Derived& operator=(Derived const&) {
    std::cout << "Derived::operator=(Derived const&)\n";
    return *this;
  }
};

int main() {
  Derived d;
  Base b;
  d = b;
}

Какие выходы:

Derived::Derived(Base const&)
Derived::operator=(Derived const&)

Примечание. Предотвращение этого?

В C ++ возможно удалить конструктор для использования в последовательностях преобразования. Для этого нужно добавить префикс объявления конструктора, используя ключевое слово explicit.

В C ++ 0x становится возможным использовать это ключевое слово и в операторах преобразования (operator T()).

Если здесь мы используем explicit до Derived::Derived(Base const&), то код становится некорректным и должен быть отклонен компилятором.

2 голосов
/ 28 мая 2011

Так как вы определили конструктор для Derived, который принимает тип Base, и вы используете понижающую версию Base, компилятор выбирает наиболее подходящий конструктор для upcast, который в данном случае является Dervied (const Base & b), который вы используете. определены. Если вы не определили этот конструктор, вы фактически получите ошибку компиляции при попытке выполнить присваивание. Для получения дополнительной информации вы можете прочитать следующее на Linuxtopia .

1 голос
/ 28 мая 2011

Он не может присвоить значение другого типа, поэтому сначала он должен создать временное значение Derived.

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