Чому я можу використовувати авто для приватного типу?


139

Мене якось здивувало, що наступний код збирається та працює (vc2012 & gcc4.7.2)

class Foo {
    struct Bar { int i; };
public:
    Bar Baz() { return Bar(); }
};

int main() {
    Foo f;
    // Foo::Bar b = f.Baz();  // error
    auto b = f.Baz();         // ok
    std::cout << b.i;
}

Чи правильно, що цей код складається добре? І чому це правильно? Чому я можу використовувати autoприватний тип, тоді як я не можу використовувати його ім’я (як очікувалося)?


11
Зауважте, що f.Baz().iце також добре, як є std::cout << typeid(f.Baz()).name(). Код поза класом може "бачити" тип, що повертається, Baz()якщо ви можете отримати його, ви просто не можете його назвати.
Стів Джессоп

2
І якщо ви думаєте, що це дивно (що, напевно, ви робите, бачачи, як ви про це просите), ви не єдиний;) Ця стратегія може бути корисною для таких речей, як ідіома Safe-Bool .
Матьє М.

2
Я думаю, що слід пам’ятати, що privateє зручність для опису API таким чином, що компілятор може допомогти виконувати їх. Він не призначений для запобігання доступу до цього типу Barкористувачами Foo, тому він жодним чином не перешкоджає Fooпропонувати цей доступ шляхом повернення екземпляра Bar.
Стів Джессоп

1
"Чи правильно, що цей код добре складається?" Ні. Вам потрібно #include <iostream>. ;-)
LF

Відповіді:


113

Правила autoздебільшого такі ж, як і для виведення типу шаблону. Опублікований приклад працює з тієї ж причини, що ви можете передавати об'єкти приватних типів у функції шаблону:

template <typename T>
void fun(T t) {}

int main() {
    Foo f;
    fun(f.Baz());         // ok
}

І чому ми можемо передати об’єкти приватних типів до функцій шаблону? Тому що лише назва типу недоступна. Сам тип все ще використовується, тому ви можете взагалі повернути його до клієнтського коду.


32
І щоб побачити, що конфіденційність імені не має нічого спільного з типом , додайте public: typedef Bar return_type_from_Baz;до класу Fooу питанні. Тепер тип можна ідентифікувати за загальним іменем, незважаючи на те, що визначено в приватному розділі класу.
Стів Джессоп

1
Якщо повторити пункт @ Steve: специфікатор доступу для імені не має нічого спільного з його типом , як видно, додавши private: typedef Bar return_type_from_Baz;в Foo, як показано . typedefD ідентифікатори не звертають уваги на специфікатори доступу, державні та приватні.
Даміен

Це для мене немає сенсу. Ім'я типу просто псевдонім для фактичного типу. Що має значення, якщо я його називаю Barабо SomeDeducedType? Це не так, як я можу ним скористатися, щоб потрапити до приватних членів class Fooчи що завгодно.
einpoklum

107

Контроль доступу застосовується до імен . Порівняйте цей приклад зі стандартом:

class A {
  class B { };
public:
  typedef B BB;
};

void f() {
  A::BB x; // OK, typedef name A::BB is public
  A::B y; // access error, A::B is private
}

12

На це питання вже дуже добре відповіли холод і Р. Мартіно Фернандес.

Я просто не міг пропустити можливість відповісти на запитання за аналогією Гаррі Поттера:

class Wizard
{
private:
    class LordVoldemort
    {
        void avada_kedavra()
        {
            // scary stuff
        }
    };
public:
    using HeWhoMustNotBeNamed = LordVoldemort;

    friend class Harry;
};

class Harry : Wizard
{
public:
    Wizard::LordVoldemort;
};

int main()
{
    Wizard::HeWhoMustNotBeNamed tom; // OK
    // Wizard::LordVoldemort not_allowed; // Not OK
    Harry::LordVoldemort im_not_scared; // OK
    return 0;
}

https://ideone.com/I5q7gw

Дякую Квентіну, що нагадав мені про лазівку Гаррі.


5
Чи не friend class Harry;пропав там?
Квентін

@Quentin ви абсолютно праві! Для повноти, ймовірно, слід також додати friend class Dumbledore;;)
jpihl

Гаррі не показує, що йому не страшно, дзвонивши Wizard::LordVoldemort;в сучасний C ++. Натомість він дзвонить using Wizard::LordVoldemort;. (Не дуже природно використовувати Волдеморта, чесно. ;-)
LF

8

Для того, щоб додати інші (хороші) відповіді, ось приклад з C ++ 98 , який показує , що проблема на самому ділі не потрібно робити з autoвзагалі

class Foo {
  struct Bar { int i; };
public:
  Bar Baz() { return Bar(); }
  void Qaz(Bar) {}
};

int main() {
  Foo f;
  f.Qaz(f.Baz()); // Ok
  // Foo::Bar x = f.Baz();
  // f.Qaz(x);
  // Error: error: ‘struct Foo::Bar’ is private
}

Використання приватного типу не заборонено, він лише називав тип. Наприклад, створити неназваний тимчасовий тип цього типу нормально, наприклад, у всіх версіях C ++.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.