Почему класс с закрытым конструктором не предотвращает наследование от этого класса?Как контролировать, какие классы могут наследоваться от определенной базы? - PullRequest
27 голосов
/ 05 апреля 2019
class B {
private:
    friend class C;
    B() = default;
};

class C : public B {};
class D : public B {};

int main() {
    C {};
    D {};
    return 0;
}

Я предположил, что, поскольку только класс C является другом B, а конструктор B является закрытым, то действительным является только класс C, а экземпляру D не разрешено создавать экземпляры.B.Но это не так работает .Где я ошибаюсь в своих рассуждениях и как добиться такого контроля над тем, какие классы могут подклассировать определенную базу?

Обновление: как отмечалось другими в комментариях, приведенный выше фрагмент работает так же, как и я.изначально ожидается под C ++ 14, но не C ++ 17.Изменение экземпляра на C c; D d; в main() работает также, как и ожидалось, в режиме C ++ 17.

Ответы [ 2 ]

24 голосов
/ 05 апреля 2019

Это новая функция, добавленная в C ++ 17. То, что происходит, C теперь считается совокупностью. Поскольку это агрегат, ему не нужен конструктор. Если мы посмотрим на [dcl.init.aggr] / 1 , то получим, что агрегат равен

Агрегат - это массив или класс с

  • нет пользовательских, явных или унаследованных конструкторов ([class.ctor]),

  • нет личных или защищенных нестатических элементов данных (пункт [class.access]),

  • без виртуальных функций и

  • нет виртуальных, частных или защищенных базовых классов ([class.mi]).

[Примечание. Совокупная инициализация не позволяет получить доступ к защищенным и закрытым членам или конструкторам базового класса. - конец примечания]

И мы проверяем все эти пункты. У вас нет конструкторов, объявленных в C или D, поэтому есть маркер 1. У вас нет элементов данных, поэтому второй маркер не имеет значения, а ваш базовый класс общедоступен, поэтому третий маркер удовлетворены.

Изменения, произошедшие между C ++ 11/14 и C ++ 17, которые позволяют это, заключаются в том, что агрегаты теперь могут иметь базовые классы. Вы можете увидеть старую формулировку здесь , где прямо указано, что базовые классы не допускаются.

Мы можем подтвердить это, проверив черту std::is_aggregate_v как

int main()
{
    std::cout << std::is_aggregate_v<C>;
}

который напечатает 1.


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

C c{};
C c1;
C c2 = C();

Как допустимые способы инициализации C. Поскольку D не является другом B, единственное, что работает, это D d{};, поскольку это агрегатная инициализация. Все остальные формы пытаются инициализировать по умолчанию, и это невозможно сделать, так как D имеет удаленный конструктор по умолчанию.

0 голосов
/ 05 апреля 2019

С Каков доступ по умолчанию для конструктора в c ++ :

Если для класса X нет объявленного пользователем конструктора, конструктор без параметров неявно объявляется как дефолтный. Неявно объявленный конструктор по умолчанию является встроенным открытым членом своего класса.

Если определение класса явно не объявляет конструктор копирования, он объявляется неявно. [...] Неявно объявленный конструктор копирования / перемещения является встроенным открытым членом своего класса.

Конструкторы для классов C и D генерируются внутренне компилятором.

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

...