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

Заголовок — только одна из немногих вещей, в которых я запутался в следующем примере:

struct A {
A() {
std::cout << "Default constructor of A" << std::endl;
}
A(const A &) {
std::cout << "Copy constructor of A" << std::endl;
}
};

struct B : private A {
// using A::A; // does not help for question 2.
};

int main() {
A a;
B b;
B c(b); // Does not work with `a` as an argument
return 0;
}

Этот пример выводит:

Default constructor of A
Default constructor of A
Copy constructor of A

Вопросы:

  1. Почему частные унаследованные конструкторы B доступны в main? Этот вопрос похож на тот, что в этот пост, но там был вопрос об использовании этого конструктора изнутри B, который отличается.
  2. Копируемый конструктор принимает const A & аргумент. Однако, если я напишу B c(a) вместо B c(b)код не компилируется. Как так? (Обратите внимание, что без комментариев using директива в B не помогает).
  3. Это незначительно, но все же. Почему компилятор не предупреждает меня о неиспользуемых переменных a а также c?

Вопрос 2 был перенесен в другой сообщение.

-3

Решение

  1. Вы не вызываете конструктор A напрямую, конструктор по умолчанию B делает это за вас. Если бы это работало иначе, вы бы никогда не смогли создать класс, который наследовал бы что-то в частном порядке.

  2. Это связано с тем, что B не имеет конструктора копирования для типа A. Единственный конструктор, который может применяться здесь, — это конструктор копирования (по умолчанию) типа B, который принимает B в качестве аргумента.

Конструкторы привязаны к своему классу, они не наследуют в том же смысле, что и функции. Как объяснить … Основная цель — убедиться, что каждый класс всегда конструируется полностью. Таким образом, чтобы построить A любого типа (независимо от того, является ли он автономным или как часть B), необходимо запустить конструктор A. Точно так же, чтобы построить объект класса B, конструктор B должен быть запущен. Если вы «унаследуете» конструктор A :: A (), вы сможете создать B, которые не были полностью построены. В этом случае конструктор A :: A () не выполнил ни одной части последовательности построения для B, оставив B в недопустимом состоянии.

Давайте попробуем другой источник. Мы оставляем A как есть и меняем B следующим образом:

struct B : private A {
B () { val = 42; }
void foo () { if (val != 42) abort (); }

using A::A;

int val;
};

Теперь допустим, что мы получили B, не создавая его:

B b (a); // illegal, but for the sake of argument.
b.foo ();

Мы указали, что мы создаем этот B, используя конструктор A :: A, поэтому единственный код, который будет выполняться, — это A :: A (). В частности, B :: B () не выполняется, поэтому val будет иметь любое значение, которое присутствовало в стеке в то время. Другими словами, вероятность того, что это будет 42, равна 1 к 2 ^ 32, что маловероятно.

Что будет, когда вызывается B.foo ()? Объект не находится в допустимом состоянии (val не 42), поэтому приложение прерывается. К сожалению!

Это, конечно, надуманный пример, но он показывает, что использование неструктурированного объекта — очень плохая вещь, и поэтому язык не позволяет вам создавать такие объекты.

  1. Они не являются неиспользованными. В конструкторах происходит всякое действие (запись в cout), которое просто невозможно устранить.

Этот шаблон проектирования встречается много раз в C ++, например в std :: mutex. Простого объявления блокировки достаточно для блокировки и разблокировки, но нет необходимости ссылаться на блокировку после объявления. Так как это общий шаблон проектирования, предупреждение будет неуместным.

Только если компилятор может доказать, что создание и уничтожение локальной переменной не имеет побочных эффектов и обнаружит, что она не используется, вы можете увидеть предупреждение (в зависимости от компилятора).

2

Другие решения

Почему частные унаследованные конструкторы B доступны в main?

Они не.

Они, однако, доступны для B сам по себе, потому что они были, ну, в общем, унаследованы, и именно B вызывает унаследованные базовые конструкторы.

Очевидно, что если бы это было не так, то частное наследование было бы совершенно бесполезным, потому что вы никогда не смогли бы создать экземпляр частного наследующего класса.

Их вызывают, и результат является доказательством этого

Нет, это не так. Вы спешите с выводами. Есть промежуточные вызовы функций (неявно определенные конструкторы в B), которые не дают результатов, которые вы не смогли рассмотреть.

Конструктор копирования, который вызывается, принимает const A & аргумент. Однако, если я пишу B c (a) вместо B c (b), код не компилируется. Как так?

Нет, это не так. Ваша строка кода вызывает конструктор копирования в B это занимает const B& аргумент. B не имеет конструктора копирования, который принимает const A& аргумент.

Этот конструктор копирования, в свою очередь, внутренне вызывает базовый конструктор копирования, создавая вывод, который вы видите.

Конструкторы не наследуются так, как вы, кажется, верите.

Это незначительно, но все же. Почему же компилятор не предупреждает меня о неиспользуемых переменных a и c?

Потому что они не неиспользованными. Их конструкция что-то сделала (создала выход).

1

По вопросам рекламы [email protected]