Не отдавай свои внутренние органы? [C ++] - PullRequest
10 голосов
/ 17 декабря 2009

Я читаю книгу под названием «Стандарт кодирования C ++» Херба Саттера, Андрея Александреску, и в главе 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). Но сокрытие данных и последующее раздача им ручек самоубийственно, подобно блокировке дома и оставлению ключей в замке. Это потому что:

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

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

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

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

это краткое изложение этой книги:

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

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

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

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

Спасибо.

Ответы [ 10 ]

32 голосов
/ 17 декабря 2009

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

9 голосов
/ 17 декабря 2009

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

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

4 голосов
/ 17 декабря 2009

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

  int handle_; // perhaps an OS resource handle

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

4 голосов
/ 17 декабря 2009

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

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

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

3 голосов
/ 17 декабря 2009

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

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

1 голос
/ 18 декабря 2009

«Общее изменяемое состояние».

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

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

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

1 голос
/ 18 декабря 2009

Вот фон на ручках в целом:

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

Дескрипторы - это непрозрачные ссылки на ресурсы (т. Е. Расположение в памяти), и только подсистема, предоставившая вам дескриптор, знает, как дескриптор связан с физическим указателем. Это не значение, не указатель и не ссылка, это просто псевдоним ресурса, который вы используете с API, который знает, что с ним делать.

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

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

Примером этого может служить библиотека Microsoft MFC C ++, в которой класс CWnd имеет метод доступа, который возвращает HWND окна (т. Е. Дескриптор):

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

1 голос
/ 17 декабря 2009

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

1 голос
/ 17 декабря 2009

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

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

0 голосов
/ 17 декабря 2009

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

...