С++ - Что такое точка вложенных классов?

Я изучаю немного С++, и теперь я борюсь с ним, подобно мне с Java. Я знаю цель внутренних классов в Java, но теперь я пытаюсь использовать вложенные классы в С++, , и я обнаруживаю, что частные атрибуты класса "container" не являются видимыми по вложенному классу, поэтому почему Я должен их использовать? Кроме того, есть ли способ сделать эти атрибуты видимыми?

Ответ 1

Я изучаю немного С++, и теперь я борюсь с ним, соглашаясь с Java.

Прежде всего помните, что вложенные классы С++ похожи на то, что в Java вы называете статическими вложенными классами. В синтаксисе С++ нет ничего, чтобы воспроизводить Java-вложенные классы.

Я обнаружил, что частные атрибуты класса "container" не видны внутренним классом...

С++ 98

В С++ внутренние классы не отличаются от обычных классов, они не являются членами класса, тогда они не могут получить доступ к закрытым членам класса контейнера (в отличие от других языков, таких как Java или С#).

С++ 03

Вложенные классы являются членами класса, но ограничения на доступ к ним все еще применяются (см. также раздел Странные вещи в конце этого ответа). Он считался стандартным дефектом (см. DR45), тогда некоторые компиляторы ранее реализовали правило доступа С++ 0x раньше даже при компиляции для С++ 03 (особенно GCC, благодаря Джонатану Вакели, чтобы это выяснить).

С++ 11

Это правило изменилось в С++ 11, теперь вложенные классы могут получить доступ к частному члену класса контейнера. Из §11.7:

Вложенный класс является членом и как таковой имеет те же права доступа, что и любой другой член.

Конечно, вам все равно нужен экземпляр для доступа к нестационарным членам.


... так почему я должен их использовать?

Затем они являются деталями реализации для группировки связанных классов, и у них одинаковые проблемы с их использованием, которые могут иметься на других языках (ясность для новичков, первичная). Их наибольшая польза от ИМО - инкапсуляция, если, например, у вас есть это:

class stream {
    virtual void write(const std::string text) = 0;
};

class channel {
public:
    virtual stream* get_stream() = 0;

    // Other methods...
};

class tcp_channel : public channel {
public:
    virtual stream* get_stream() {
        return new tcp_stream(this);
    }

private:
    class tcp_stream : public stream { /* implementation */ };
};

В некоторых случаях они также полезны для замены вложенных пространств имен:

class protocol {
public:
    virtual void create_connection() = 0;

    class tcp : public protocol { /* implementation */ };
    class shared_memory : public protocol { /* implementation */ };
    class named_pipes: public protocol { /* implementation */ };
};

auto media = protocol::tcp();

Или, чтобы скрыть детали реализации:

class file_system_entry {
public:
    class file : public file_system_entry { };
    class directory : public file_system_entry { };

    std::time_t get_last_modified() { ... }

    void remove() { ... }
    virtual void copy_to(std::string path) = 0;

private:
    class local_handle {
        // Implementation details
    } _handle;
};

Есть много других шаблонов использования (см. также Почему бы вам использовать вложенные классы в С++? для более удобного обсуждения), просто помните, что не все будут правильно понимать ( и используйте!) их. См. Также Плюсы и минусы использования вложенных классов С++ и перечислений?

Кроме того, есть ли способ сделать видимыми эти атрибуты?

До С++ 11 вы не можете (конечно, если вы не объявите их как friend, но смотрите следующий параграф), если вам нужна эта функция, просто используйте компилятор С++ 11 (который поддерживает эту функцию). GCC делает (давным-давно), а также MSVC, я не знаю о других компиляторах.

Вложенные друзья

Есть ли разница между правилами доступа С++ 11 и классами друзей? В целом они почти эквивалентны (автоматический доступ только менее подробный):

class container {
public:
    class nested;
    friend class nested;

    class nested { };
};

По сравнению с:

class container {
public:
    class nested { };
};

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

class external : public container::nested {
public:
    // No: only class declared inside "container"
    // has access to private members, we do not inherit that 
    void foo(container obj) { /* access a private member of obj*/ }
};

// No, "container" has not access to "nested" private members,
// visibility isn't reciprocal
void container::foo(container::nested obj) {
    // Access some private member of obj
}

// No, we don't have anything to do with container,
// visibility isn't transitive
void friendOfNested(container obj) {
    // Access some private member of obj
}

Значит ли полностью эквивалентно? Нет, потому что частные члены друзей container доступны в nested, если это вложенный класс в С++ 11, но это не так, если nested является другом container. Учитывая эту очерченную структуру:

class container;

class another {
    friend class container;     
};

class container {
public:
    class nested { };   
};

nested может получить доступ к another частным членам:

void container::nested::foo(another obj) {
    obj.somePrivateMember = 0;
}

Это работает, потому что nested является членом container, тогда транзитивное ограничение дружбы не применяется. Прежде чем С++ 11, объявив nested в качестве друга container, этот код не будет компилироваться, потому что дружба не является транзитивной.

Странные вещи

Предположим, мы всегда можем объявить вложенный класс как друга своего контейнера? Фактически стандартный указанный (SO/IEC 14822: 2003 (E), 11.8):

Друг класса - это функция или класс, который не является членом класса...

Тогда мы не сможем объявить nested в качестве друга container: в С++ 03 вложенные классы являются членами класса (но стандарт явно говорит, что у них нет доступа к контейнерам-рядовым, а также они не могут быть друзьями класса контейнера). Кажется, не было никакой надежды, к счастью, большинство компиляторов разрешили нам это делать (независимо от того, что сказал стандарт).

Ответ 2

Он обеспечивает еще один хороший метод инкапсуляции. Размещение одного класса целиком в пространстве имен другого класса снижает его видимость для других частей вашей базы кода. Это помогает достичь масштабируемости и снижает нагрузку на обслуживание.

Объекты функций часто кодируются таким образом.

Ответ 3

Разные не то же самое.

Java внутренние классы создают объекты, которые предположительно связаны с объектом класса external, поэтому доступ к членам внешнего класса из методов внутреннего класса может быть сделано без явного создания указателя или ссылки. С++ этого не делает; вложенный класс - это просто класс, определение которого вложено в определение другого класса. Это удобно для инкапсуляции, но что это: он не предназначен для волшебного создания объектов этого типа, которые знают об объектах содержащего типа.