Возможно ли получить доступ к закрытым членам внутреннего класса?

149
24 июля 2019, 12:20

Начало класса:

template<class T>
class List : public Collection<T> {
private:
    class Node {
    private:
        Node* _next;
        Node* _prev;
    public:
        T _data;
    };

Первая ошибка доступа:

~List() {
    while (_head) {
        _tail = _head->_next; //я не вижу _next!!!
        delete _head;
        _head = _tail;
    }
}

Не хотелось бы оставлять next и prev открытыми из очевидных предположений. Можно что-то изменить?

Answer 1

Есть разные варианты, например, сделать класс другом или добавить в него открытые функции доступа к полям.

В своем стремлении все закрыть вы сделали "вещь в себе" - класс, в котором есть только закрытые члены, к которым может обращаться только сам класс. Но если к ним может обращаться только класс, то логично попросить его об этом? Т.е. добавить функции-члены для работы с закрытыми членами-данными, нет?

Иначе зачем вам этот чемодан, закрытый на ключ, который находится в чемодане? :)

Answer 2

Согласно стандарту C++ (14.7 Nested classes):

1 A nested class is a member and as such has the same access rights as any other member. The members of an enclosing class have no special access to members of a nested class; the usual access rules (Clause 14) shall be obeyed.

То есть члены внешнего класса не имеют специальных прав доступа к членам вложенного класса.

Вы могли бы класс List объявить дружественным к вложенному классу Node.

Так как у вас класс Node вложенный и определен как private-член внешнего класса, то нет никакой необходимости объявлять еще его члены как private.

Answer 3

Вы не описываете этого в своем вопросе, но проблемы с нежелательным доступом к членам такого класса вполне возможны во вполне естественных ситуациях. Например, если ваш класс List предоставляет методы для работы с подсписками или узлами связного списка, т.е. методы вроде splice, detach и т.п. Например в таком коде

class List
{
   class Node 
   {
   public:
     Node *next;
     ...
   };
 public:
   Node *detach_first();
};

пользователь такого класса List будет иметь возможность сделать

int main()
{
  List list;
  ...
  auto *p = list.deatch_first();
  // `p->next` - свободно доступен здесь
}

и после этого получить свободный доступ к публичным полям класса List::Node. Более того, пользователь может сделать

using ListNode = std::remove_pointer_t<decltype(list.deatch_first())>;

и вот уже у вас в руках есть сам закрытый тип list::Node, без применения каких-либо "хаков".

Помещение класса List::Node в приватный раздел класса List лишает внешних пользователей лишь возможности впрямую упоминать имя List::Node во внешнем коде, но не более того. Вышеприведенный код, например, использует auto и обходится без упоминания имени List::Node, получая при этом совершенно законный доступ ко всему интерфейсу List::Node. То есть никакой ситуации "закрытого чемодана в закрытом чемодане" тут нет. Вам лишь запрещено называть внутренний чемодан "чемоданом". (Все это, кстати, реализуемо и в С++98, без auto и decltype, хоть там это и требует дополнительных телодвижений.)

Тот факт, что защита, предоставляемая таким закрытым объявлением вложенного класса, настолько "тонка" и узконаправленна, зачастую является сюрпризом даже для подготовленных С++ программистов.

Однако, возвращаясь к исходной задаче (в моем ее понимании):

  1. Если ваш класс List, согласно вашему замыслу, действительно должен давать внешним пользователям доступ к значениям типа List::Node, то имеет смысл все таки сделать List::Node открытым (public) членом List - скрывать его никакого смысла нет.

    Если вы хотите при этом запретить доступ к реализации List::Node, то следует сделать все члены List::Node закрытыми (private), а класс List сделать другом класса List::Node.

  2. Но лучше все таки пойти по другому пути: оставить класс List::Node закрытым в List и сделать все члены List::Node открытыми. Последнее позволит List доступаться к членам List::Node. Но при этом надо позаботиться о том, чтобы внешний пользователь никогда не получал прямого доступа к значениям типа List::Node. Все внешние методы, вроде detach_fist, splice и т.д. должны работать не через List::Node *, а через публичный List::NodeHandle - некий opaque тип, который прячет внутри себя значение List::Node *, но не открывает этого факта внешнему пользователю. То есть в данном случае мы предпринимаем дополнительные усилия для того, чтобы "закрытый чемодан" действительно был закрытым.

    Например, простейшим вариантом такого List::NodeHandle будет просто void *. Внутри методов List вы просто будете приводить его к List::Node *. В тривиальных контейнерах в качестве List::NodeHandle может выступать их итератор. В более сложных может понадобится отдельная абстракция. Обратите внимание, что начиная с С++17 стандартная библиотека С++ поддерживает подобный интерфейс для классов стандартных ассоциативных контейнеров, основанный именно на "node handle" подходе: https://en.cppreference.com/w/cpp/container/node_handle

READ ALSO
Как найти число, которое встречается во всех строках матрицы?

Как найти число, которое встречается во всех строках матрицы?

Задан целочисленный массив N*M (N - количество строк, M - столбцов)Каждая строка массива упорядочена по возрастанию

153
node-ffi - Передать указатель на строку в dll

node-ffi - Передать указатель на строку в dll

В dll определена функция:

155
Способы хранения данных в С++

Способы хранения данных в С++

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

166