Конструктор доступен за пределами частного наследуемого типа?

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

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 был перенесен в другой сообщение.


person AlwaysLearning    schedule 31.07.2016    source источник
comment
Как получается, что частные конструкторы B доступны в main? Что заставляет вас думать, что они?   -  person juanchopanza    schedule 31.07.2016
comment
@juanchopanza Им звонят, и результат является тому доказательством.   -  person AlwaysLearning    schedule 31.07.2016
comment
Это не значит, что они доступны в main. Или вы имеете в виду, что приватному члену нельзя позвонить?   -  person juanchopanza    schedule 31.07.2016
comment
@juanchopanza Да, это то, что я имею в виду.   -  person AlwaysLearning    schedule 31.07.2016
comment
Итак, какой смысл в частных членах, если их никогда нельзя будет вызвать? Может их тоже убрать, нет?   -  person juanchopanza    schedule 31.07.2016
comment
@juanchopanza *построено.   -  person LogicStuff    schedule 31.07.2016
comment
@juanchopanza Прости. Я не понимаю. Можете ли вы вызвать частную функцию-член из автономной функции, не являющейся другом? Я так не думаю.   -  person AlwaysLearning    schedule 31.07.2016
comment
@AlwaysLearning Нет. Но вы можете вызвать его из класса, где член является закрытым. Что здесь и происходит.   -  person juanchopanza    schedule 31.07.2016
comment
ОДИН вопрос на вопрос, пожалуйста. Подсказка в названии.   -  person Lightness Races in Orbit    schedule 31.07.2016


Ответы (2)


  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 (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. Простого объявления блокировки достаточно для блокировки и разблокировки, но нет необходимости ссылаться на блокировку после объявления. Поскольку это распространенный шаблон проектирования, предупреждение было бы неуместным.

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

person H. Guijt    schedule 31.07.2016
comment
Во-первых, спасибо за ответ на вопрос. В 2: я получаю ту же ошибку, даже если у меня есть using A::A в B, что должно сделать доступным конструктор копирования, принимающий const A &. В 3: я совершенно уверен, что компилятор обычно жалуется на объекты, которые создаются, но не используются, и мне нужно написать что-то вроде (void)a, чтобы его скомпилировать (я компилирую с g++ с -pedantic -Werror). - person AlwaysLearning; 31.07.2016
comment
Спасибо за дополнение, но все же, что произойдет, если я раскомментирую это объявление using в B? Почему вызов конструктора с const A & не разрешен? - person AlwaysLearning; 31.07.2016
comment
Потому что B тоже нужно построить. A::A() только конструирует A. Кто-то должен пойти и убедиться, что B также находится в допустимом состоянии, прежде чем мы сможем продолжить. Таким образом, конструктор для B должен работать; просто вызвать конструктор A недостаточно. - person H. Guijt; 31.07.2016
comment
Вы имеете в виду, что конструкторы, включенные в оператор using, также косвенно вызываются конструкторами B и не могут использоваться напрямую? - person AlwaysLearning; 31.07.2016
comment
По сути, да. Конструкторы (и деструкторы) особенные по-разному, в том числе и в этом. - person H. Guijt; 31.07.2016
comment
Вот пример, который, я думаю, показывает, что это не так: ideone.com/Lc4RAA. Здесь B не имеет конструктора, принимающего int, но мы все равно можем создать объект B, используя int в качестве аргумента. Почему const A & отличается? - person AlwaysLearning; 31.07.2016
comment
Я думаю, что пришло время перенести это в другой пост: stackoverflow.com/q/38685216/2725810 - person AlwaysLearning; 31.07.2016

Как получается, что частные конструкторы B доступны в main?

Это не так.

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

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

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

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

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

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

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

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

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

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

person Lightness Races in Orbit    schedule 31.07.2016
comment
Конструкторы не наследуются так, как вы, кажется, думаете. Но та же ошибка возникает, даже если у меня есть using A::A в B... - person AlwaysLearning; 31.07.2016
comment
@AlwaysLearning: Что это меняет? - person Lightness Races in Orbit; 01.08.2016