auto со вложенными приватными классами
Сегодня писал класс для работы с памятью и мне потребовалось использовать в нём другой вложенный класс. В главном классе был метод который возвращает объект этого вложенного класса:
class Main
{
private:
class Inner
{
};
public:
Inner foo()
{ return {}; }
};
Потом я сделал так:
Main m;
auto a = m.foo();
И это сработало. Но на прямую создать такой объект нельзя, т. к. вложенный класс - приватный:
Main::Inner a; //ошибка
Если честно я немного в замешательстве и меня интересует, дефект это или так и было задумано ( просто я до этого момента нигде не видел такого ).
Ответы (2 шт):
Потому что public/private контролирует только имена и только на этапе компиляции. А auto в данном случае как бы не попадает под это правило, так как компилятор грубо говоря делает typedef. А typedef может "поменять доступность" функции.
Почему так? видимо потому, что auto в большинстве случаев копирует поведение с шаблонных вещей, а там (в шаблонной магии) бывает много случаев, когда тип с одной стороны скрыт, а с другой виден.
Вот например, такой пример (он есть по ссылке ниже):
template <typename T>
void fun(T t) {}
int main() {
Foo f;
fun(f.Baz()); // ok
}
Тип f.Baz() может быть совсем неизвестен функции fun, но это работает.
Ещё раз. private/public действует на имена, но не на сами типы, хотя в головах большинства людей, это одно и тоже. а auto как бы дает ещё одно имя.
Такой же вопрос на enSO
Да, это действительно один из дефектов механизма контроля доступа в С++. Однако использование auto тут никакой роли не играет. На строке auto a = m.foo(); просто вызывается public функция без каких-либо ограничений доступа. Проблемным местом на самом деле является использование идентификатора с private доступом при объявлении чего-то c public доступом. На строке public: Inner foo() контроль доступа это разрешает, хотя по сути тут происходит вываливание приватной начинки класса наружу и ранее заданный модификатор доступа для Inner становится бесполезен.
В более толковых языках, например в c# или kotlin, контроль доступа реализован более ответственно, и попытки использовать идентификатор с более ограниченным доступом при объявлении чего-то с более широким доступом приведут к ошибке сборки. В С++ предлагали ввести подобное улучшение, однако это бы поломало обратную совместимость (как и большинство дельных предложений). Пока можно воспользоваться рекомендацией из Misra C++: размещать объявления в классе строго в порядке public, protected, private. При таком подходе в секциях с более широким доступом имена с более ограниченным доступом использоваться не могут, так как они будут объявлены позднее:
class Main
{
public:
Inner foo() // error: 'Inner' does not name a type
{
return {};
}
private:
class Inner
{
};
};