Не отдавать свои внутренности? [C++]

Я читаю книгу, названную "стандарт кодирования C++" Herb Sutter, Andrei Alexandrescu и в главе 42 этой книги является примером: (глава коротка, таким образом, я беру на себя смелость и вставляю часть ее),

Рассмотрите:

 class Socket {
 public:
   // … constructor that opens handle_, destructor that closes handle_, etc. …
   int GetHandle() const {return handle_;} // avoid this - (1) <-why this is bad code?
                                           // and why there is a comment to avoid such code??
 private:
   int handle_; // perhaps an OS resource handle
 };

Сокрытие данных является мощной абстракцией и устройством модульного принципа (см. Объекты 11 и 41). Но сокрытие данных и затем отдача дескрипторов к нему являются пагубными, точно так же, как блокировка Вашего дома и отъезд ключей в блокировке. Это вызвано тем, что:

У клиентов теперь есть два способа реализовать функциональность: Они могут использовать абстракцию Вашего класса (Сокет) или непосредственно управлять реализацией, на которую Ваш класс полагается (дескриптор C-стиля сокета). В последнем случае объект не знает о существенных изменениях к ресурсу, он думает, что он владеет. Теперь класс не может надежно обогатить или украсить функциональность (например, проксирование, вход, собирая статистические данные), потому что клиенты могут обойти украшенный, управляемый implementationand любой из инвариантов, это думает, что добавляет, который делает корректную обработку ошибок почти невозможной (см. Объект 70).

Класс не может изменить конкретную реализацию своей абстракции, потому что клиенты зависят от него: Если Сокет будет позже обновлен для поддержки другого протокола с другим набором примитивов низкого уровня, то код вызова, который выбирает базовый handle_ и управляет им неправильно, будет тихо поврежден.

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

Клиентский код может сохранить дескрипторы, которые Ваш класс возвращает, и попытка использовать их после того, как код Вашего класса делал недействительным их.

это - сводка из этой книги:

Не добровольно вызывайтесь слишком много: Постарайтесь не возвращать дескрипторы внутренним данным, управляемым Вашим классом, таким образом, клиенты неудержимо не изменят состояние, что Ваш объект думает, что это владеет.

В основном то, что я прошу:

  1. Почему строка, отмеченная мной как (1), перечислена как пример плохого кода (я всегда думал, что возврат указателей или ссылки является плохой идеей, но возврат значением в порядке. Здесь они говорят, что возврат значением является плохой идеей также?)

  2. Действительно ли возможно, что существует '&' пропавшие без вести и что они действительно имеют в виду, не должен возвращать внутренние данные ссылкой или указателями?

Спасибо.

10
задан Martin York 17 December 2009 в 20:28
поделиться

10 ответов

Я думаю, вам не хватает того, что дескриптор - даже если он представлен int в системе типов - является ссылкой на что-то. Это не возвращает какое-то информационное значение - это возвращает внутреннюю ссылку объекта на системный ресурс. Класс должен сам управлять этим дескриптором, и на дескриптор должен влиять внешний мир только через интерфейс класса.

32
ответ дан 3 December 2019 в 13:19
поделиться

Here is background on handles in general:

http://www.anvir.com/handle.htm

Handles are opaque references to resources (i.e. memory location) and only the subsystem that gave you the handle knows how the handle is associated with a physical pointer. It is neither value nor pointer nor reference, it's just an alias for a resource that you use with the API that knows what to with it.

So what the book is trying to say is that when you have a class that manages some resource you are supposedly adding a layer of abstraction. However if you give away a handle to a resource, you really don't abstract away the implementation, since your abstraction can be easily circumvented.

A requirement to have handles and functions that take handles as parameters to perform a certain task is mainly dictated by procedural languages like C, which do not have objects and hence cannot hide a certain resource inside a class and only provide you with methods to operate on that resource.

An example of this could be Microsoft MFC C++ library where CWnd class has an accessor that return the window's HWND (i.e. handle):

http://msdn.microsoft.com/en-us/library/d64ehwhz(VS.71).aspx

1
ответ дан 3 December 2019 в 13:19
поделиться

«Совместное изменяемое состояние».

Передавая дескриптор обратно, API создает совместно используемое изменяемое состояние, чего следует по возможности избегать.

Рассмотрите альтернативный API, который предоставляет метод Close (), а не GetHandle (). Никогда не открывая дескриптор напрямую, класс гарантирует, что он будет единственным, кто закроет дескриптор. Дескриптор становится частным состоянием класса.

Может показаться, что это приведет к раздуванию вашего API, но есть реальное преимущество - тогда класс будет знать, что любые изменения, внесенные в состояние, прошли через него. . В существующей реализации он должен проверять статус дескриптора каждый раз, когда он что-либо делает, поскольку он не владеет этим состоянием. Скрывая дескриптор, класс полностью избавляется от этой ошибки.

1
ответ дан 3 December 2019 в 13:19
поделиться

Проблема не в деталях низкого уровня (в этом случае код отлично подходит для C ++)

Проблема в том, что вы нарушаете свою абстракцию. Что, если в будущем вам нужно будет вместо дескриптора int изменить его на какой-то указатель. Вы не сможете внести это изменение, не сломав ни одного клиента, использующего ваш класс.

9
ответ дан 3 December 2019 в 13:19
поделиться

Дело не в том, что вы возвращаете по значению, и это нормально, дело в том, что вы возвращаете дескриптор ресурса.

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

Если ресурс является, например, файлом, ваш класс должен иметь методы write () и read (), которые считывают и записывает в / из файла.

4
ответ дан 3 December 2019 в 13:19
поделиться

1) Они говорят о возврате дескриптора сокета. Во многих случаях и int будет нормально (например, размер массива или что-то в этом роде), но в этом случае int можно использовать для вызова функций C нижнего уровня, которые изменят сокет без ведома вашего класса. Все, что позволяет базовому представлению вашего класса изменяться без его ведома, - это плохой дизайн, как говорится в главе.

2) Я сомневаюсь, что у них отсутствует ссылка по причинам, указанным выше

3
ответ дан 3 December 2019 в 13:19
поделиться

Обратите внимание на объявление handle_ :

  int handle_; // perhaps an OS resource handle

Даже если вы возвращаете int по значению с точки зрения C ++, с точки зрения ОС этот дескриптор «ссылка» на некоторый ресурс ОС.

4
ответ дан 3 December 2019 в 13:19
поделиться

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

1
ответ дан 3 December 2019 в 13:19
поделиться

Вы правы, когда вы говорят, что возвращение ссылок и указателей на частные члены - плохая практика, но здесь также плохо возвращать значение, потому что значение имеет внутреннее значение. Дескриптор можно рассматривать как адрес объекта, адрес объекта, управляемый глубоко внутри операционной системы.

Разрешение внешним классам доступа к этому дескриптору будет плохой новостью. Представьте, что это дескриптор файла. Внешний класс теперь может закрыть ваш файл (зная его дескриптор), и ваш класс упаковки ничего об этом не узнает. Теперь внутренние компоненты вашего класса находятся в недопустимом состоянии.

1
ответ дан 3 December 2019 в 13:19
поделиться

Суть этого примера состоит в том, что дескриптор сокета возвращается, хотя и по значению, как вы указываете. Как только вызывающий получает дескриптор, он может использовать его для выполнения системных вызовов самостоятельно, не проходя через уровень абстракции, предоставляемый вашим классом.

0
ответ дан 3 December 2019 в 13:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: