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

88

Я узнал, что никогда не могу получить доступ к частной переменной, только с помощью функции get в классе. Но тогда почему я могу получить к нему доступ в конструкторе копирования?

Пример:

Field::Field(const Field& f)
{
  pFirst = new T[f.capacity()];

  pLast = pFirst + (f.pLast - f.pFirst);
  pEnd  = pFirst + (f.pEnd - f.pFirst);
  std::copy(f.pFirst, f.pLast, pFirst);
}

Мое заявление:

private:
  T *pFirst,*pLast,*pEnd;
король демонов
источник
Потому что конструктор копирования по умолчанию является членом класса, как и некоторые другие.
DumbCoder
+ 53 / -0? Кто за это голосовал? А как еще вы бы их скопировали ?!? (Давайте разоблачим неальтернативы: создать общедоступный получатель ссылки для каждого закрытого члена? Тогда они вообще не будут закрытыми. Сделать общедоступный const&геттер или геттер по значению для каждого? для значений тратят ресурсы и терпят неудачу для не копируемых членов.) Я сбит с толку таким успехом такого бессмысленного вопроса, когда я спрашиваю о копирующей конструкции, полностью игнорируя, что это означает, и ни один ответ не использует базовую логику, чтобы опровергнуть его. Они объясняют сухие формальности, но есть гораздо более простой ответ на этот зашоренный вопрос
underscore_d
8
@underscore_d, "А как еще бы вы их скопировали?" на мой взгляд, это очень странный ответ. Это как ответить на вопрос "как работает гравитация?" с "как бы еще все упало!" Инкапсуляция на уровне классов и инкапсуляция на уровне объектов на самом деле довольно распространены. Забавно, что вы думаете, что это глупый вопрос и что ответ должен быть очевиден. Имейте в виду, что инкапсуляция в Smalltalk (возможно, архетипическом объектно-ориентированном языке) действительно работает на уровне объектов.
aioobe
@aioobe Хорошее замечание, спасибо. Мой комментарий довольно резкий - возможно, в тот день сломалась кофеварка. Я признателен за то, что вы указали, почему этот вопрос будет популярен, особенно среди тех, кто родом из других (и, возможно, других) языков OO. Фактически, можно утверждать, что мой комментарий был «зашоренным», поскольку я писал с точки зрения человека, который в основном программирует на C ++. Кроме того, мне нравится аналогия с гравитацией!
underscore_d

Ответы:

33

ИМХО, существующие ответы плохо справляются с объяснением «почему» этого - слишком много внимания уделяется повторению того, какое поведение является допустимым. «Модификаторы доступа работают на уровне класса, а не на уровне объекта». - Да, но почему?

Общая концепция здесь заключается в том, что программисты, проектирующие, пишущие и поддерживающие класс, должны понимать желаемую инкапсуляцию объектно-ориентированного программирования и уполномочены координировать ее реализацию. Итак, если вы пишете class X, вы кодируете не только то, как отдельный X xобъект может использоваться кодом с доступом к нему, но и то, как:

  • производные классы могут взаимодействовать с ним (через необязательно чистые виртуальные функции и / или защищенный доступ) и
  • отдельные Xобъекты взаимодействуют, чтобы обеспечить предполагаемое поведение, соблюдая пост-условия и инварианты вашего дизайна.

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

В частности, эти операции могут захотеть воспользоваться привилегированным доступом для выполнения таких действий, как:

  • (конструкторы копирования) используют закрытый член объекта "rhs" (правая сторона) в списке инициализаторов, так что переменная-член сама создается копией, а не создается по умолчанию (если даже допустимо), а затем назначается тоже (опять же, если законно)
  • общие ресурсы - дескрипторы файлов, сегменты разделяемой памяти, shared_ptr ссылки на данные и т. д.
  • взять на себя ответственность за вещи, например auto_ptr<> "перемещает" право собственности на строящийся объект
  • копировать частный «кэш», калибровку или члены состояния, необходимые для создания нового объекта в оптимально пригодном для использования состоянии без необходимости их регенерации с нуля
  • копировать / получать доступ к диагностической / отслеживаемой информации, хранящейся в копируемом объекте, которая иначе недоступна через общедоступные API, но может использоваться некоторым более поздним объектом исключения или журналированием (например, что-то о времени / обстоятельствах, когда "исходный" не созданный копией экземпляр был построен)
  • выполнить более эффективную копию некоторых данных: например , объекты могут иметь , например , с unordered_mapчленом , но публично только разоблачить begin()и end()итераторы - с прямым доступом к size()вам могли reserveемкость для быстрого копирования; еще хуже , если они только разоблачить at()и insert()в противном случае throw....
  • копировать ссылки обратно на родительские / координационные / управляющие объекты, которые могут быть неизвестными или доступными только для записи для клиентского кода
Тони Делрой
источник
2
Я думаю, что самая большая причина заключается в том, что проверка того, this == otherкаждый раз, когда вы обращаетесь к other.xкоторому, вы должны, если бы модификаторы доступа работали на уровне объекта, потребовало бы огромных накладных расходов во время выполнения .
aioobe
2
@aioobe Я думаю, ваш ответ должен быть более заметным. Хотя ответ Тони действительно хорош и концептуален, если бы я был человеком, делающим ставки, я готов поспорить, что ваш ответ является фактической исторической причиной выбора. Он не только более производительный, но и намного проще. Было бы отличным вопросом к Бьярну!
Нир Фридман
Я отметил ваш ответ, потому что он объясняет предысторию;)
demonking
@demonking, я думаю, что причины, приведенные в этом ответе, объясняют, почему удобно открывать личные данные для других объектов. Но модификаторы доступа не предназначены для того, чтобы сделать данные достаточно «открытыми». Они скорее предназначены для того, чтобы сделать данные достаточно закрытыми для инкапсуляции. (С точки зрения удобства было бы даже лучше, если бы частные переменные были общедоступными!) Я обновил свой ответ разделом, который, как мне кажется, лучше касается фактического почему .
aioobe
@aioobe: старые комментарии, но в любом случае ... «проверять this == otherкаждый раз, когда вы обращаетесь other.x» - упускает суть - если бы он other.xбыл принят только во время выполнения, когда эквивалентен this.x, other.xв первую очередь не было бы много записи указателя ; компилятор также может заставить вас писать if (this == other) ...this.x...то, что вы собираетесь делать. Ваша концепция «удобства (даже больше, если частные переменные были общедоступными)» также упускает из виду суть - способ, которым определены в Стандарте, достаточно ограничен, чтобы обеспечить правильную инкапсуляцию, но не излишне неудобен.
Тони Делрой
108

Модификаторы доступа работают на уровне класса , а не на уровне объекта .

То есть два объекта одного класса могут обращаться к личным данным друг друга.

Зачем:

В первую очередь за счет экономичности. Было бы немаловажными накладными расходами во время выполнения проверять, this == otherкаждый раз, когда вы обращаетесь к other.xкоторому, вам придется, если модификаторы доступа работают на уровне объекта.

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

И это невероятно удобно при написании конструкторов копирования и операторов присваивания.

aioobe
источник
35

Вы можете получить доступ к закрытым членам класса изнутри класса, даже к членам другого экземпляра.

Александр Рафферти
источник
10

Чтобы понять ответ, я хотел бы напомнить вам несколько концепций.

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

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

Надеюсь, что это ответ на ваш вопрос.

Али Заиб
источник
6

Конструктор копирования является функцией-членом класса и поэтому имеет доступ к членам данных класса, даже к тем, которые объявлены как «частные».

Боян Комазец
источник
3
Как человек, знающий язык, я понимаю, о чем вы. Однако, если бы я не знал языка, я бы подумал, что вы просто повторяете мне вопрос.
Сан-Хасинто,