указатель закрытого объекта против значения объекта и возвращение внутренних объектов объекта - PullRequest
1 голос
/ 27 июня 2010

Относится к: C ++ частный указатель "утечка"?

Согласно Effective C ++ (пункт 28), «избегайте возврата дескрипторов (ссылок, указателей или итераторов) внутренним объектам. Это увеличивает инкапсуляцию, помогает функциям-членам const действовать const и сводит к минимуму создание висячих дескрипторов».

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

Однако для возврата объекта по значению требуется конструктор копирования, который идет вразрез с Руководством по стилю Google C ++ операторов "DISALLOW_COPY_AND_ASSIGN".

Как новичок в C ++, если я что-то упустил, я нахожу эти два предложения противоречащими друг другу.

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

(Изменить) Для пояснения, пример свисающего указателя Мейерса:

class Rectangle {
public:
  const Point& upperLeft() const { return pData->ulhc; }
  const Point& lowerRight() const { return pData->lrhc; }
  ...
};

class GUIObject { ... };
const Rectangle boundingBox(const GUIObject& obj); 

Если клиент создает функцию с кодом, таким как:

GUIObject *pgo; // point to some GUIObject
const Point *pUpperLeft = &(boundingBox(*pgo).upperLeft());

"Вызов boundingBox вернет новый временный объект Rectangle [(здесь вызывается temp.)] Затем будет вызываться upperLeft на temp, и этот вызов вернет ссылку на внутреннюю часть temp, в частности, в одну из точек, составляющих его ... в конце оператора возвращаемое значение boundingBox temp будет уничтожено, что косвенно приведет к уничтожению точек temp. Это, в свою очередь, оставит pUpperLeft указывающим на объект этого больше не существует ". Мейерс, Эффективный C ++ (позиция 28)

Я думаю, что он предлагает вместо этого возвращать Point по значению, чтобы избежать этого:

const Point upperLeft() const { return pData->ulhc; }

Ответы [ 3 ]

5 голосов
/ 27 июня 2010

Руководство по стилю Google C ++, скажем так, несколько «особенное» и привело к многочисленным дискуссиям по различным группам новостей C ++.Давайте оставим это в покое.

В обычных условиях я хотел бы предположить, что следование рекомендациям в Effective C ++ обычно считается хорошей вещью;в вашем конкретном случае возвращение объекта вместо какой-либо ссылки на внутренний объект обычно является правильным решением.Большинство компиляторов довольно хорошо справляются с большими возвращаемыми значениями (Google для оптимизации возвращаемых значений, почти каждый компилятор делает это).

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

2 голосов
/ 27 июня 2010

Во-первых, давайте посмотрим на это утверждение в контексте:

Согласно действующему C ++ (пункт 28), "избегать возврата дескрипторов (ссылки, указатели или итераторы) на объект Внутренности. Это увеличивает инкапсуляцию, помогает константным функциям-членам действовать const, и сводит к минимуму создание свисающие ручки. "

Это в основном говорит о способности класса поддерживать инварианты (грубо говоря, свойства, которые остаются неизменными).

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

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

К сожалению, это не всегда работает на практике. Иногда вам приходится возвращать дескриптор или указатель или какой-либо другой внутренний элемент по ссылке по разным причинам. Давайте возьмем boost :: scoped_ptr , например. Это интеллектуальный указатель, предназначенный для управления памятью через внутренний указатель, который он хранит. У него есть метод get (), который возвращает этот внутренний указатель. К сожалению, это позволяет клиентам делать такие вещи, как:

delete my_scoped_ptr.get(); // wrong

Тем не менее, этот компромисс был необходим, потому что во многих случаях мы работаем с API-интерфейсами C / C ++, требующими передачи обычных указателей. Компромиссы часто необходимы для удовлетворения библиотек, которые не принимают ваш конкретный класс, но принимают один из его внутренних органов.

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

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

Нет, если вы можете вернуть копию, то вы можете в равной степени вернуться по константной ссылке. Клиенты не могут (при нормальных обстоятельствах) вмешиваться в такие внутренние процессы.

0 голосов
/ 27 июня 2010

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

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

Попробуйте ограничить передачу по значению, если можете, если нет необходимости.

...