Стоит ли когда-нибудь использовать защищенные переменные-члены?

97

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

Джон Ченнинг
источник

Ответы:

74

Стоит ли когда-нибудь использовать защищенные переменные-члены?

Зависит от того, насколько вы придирчивы к скрытию состояния.

  • Если вы не хотите утечки внутреннего состояния, то объявление всех ваших переменных-членов закрытыми - это лучший вариант.
  • Если вам все равно, что подклассы могут получить доступ к внутреннему состоянию, то protected достаточно хорошо.

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

Аллен Лалонд
источник
1
Можете ли вы прокомментировать производительность защищенных переменных по сравнению с частной переменной с помощью метода get / set?
Jake
3
Я бы сказал, что это не то, о чем стоит беспокоиться, если вы не обнаружите в результате профилирования, что горлышко бутылки в конечном итоге становится аксессуаром (чего почти никогда не бывает). Есть уловки, которые можно использовать, чтобы сделать JIT умнее, если это окажется проблемой. В java, например, вы можете намекнуть, что аксессор может быть встроен, пометив его как final. Хотя, честно говоря, производительность геттеров и сеттеров гораздо менее важна, чем работа с организацией системы и реальными проблемами производительности, определенными профилировщиком.
Allain Lalonde
23
@Jake: Никогда не принимайте проектные решения, основываясь на предположениях о производительности. Вы принимаете дизайнерские решения, основываясь на том, что вы считаете лучшим дизайном, и только если ваше профилирование в реальной жизни показывает узкое место в вашем дизайне, вы идете и исправляете его. Обычно, если дизайн хороший, производительность тоже хорошая.
Mecki
С закрытыми членами, кроме открытого интерфейса, они не могут видеть детали реализации. Они могут просто открыть класс и найти его, так что это не имеет смысла ?!
Black
2
@Black Очевидно, что Allain имел в виду «они не могут получить доступ » к этим членам и, следовательно, не могут создавать код против них, оставляя автору класса возможность удалить / изменить защищенные члены позже. (Конечно, идиома pimpl позволит скрыть их визуально, а также от единиц перевода, включая заголовок.)
underscore_d
31

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

У них нет особого преимущества перед защищенными методами / свойствами (когда-то у них могло быть небольшое преимущество в производительности), и они также чаще использовались в эпоху, когда в моде было очень глубокое наследование, чего сейчас нет.

Уилл Дин
источник
2
Не должно no particular advantage over protected methods/propertiesбыть no particular advantage over *private* methods/properties?
Penghe Geng
Нет, потому что я / говорил о преимуществах / недостатках различных способов связи между производными классами и их базами - все эти методы будут `` защищены '' - разница в том, являются ли они переменными-членами (полями) или свойствами / методами ( т.е. какие-то подпрограммы).
Уилл Дин
1
Спасибо за быстрое разъяснение. Я рад получить оригинальный ответ автора через час на мой вопрос к посту 6-летней давности. Вы не думаете, что такое может случиться на большинстве других онлайн-форумов :)
Penghe Geng
9
Еще более примечательно то, что я на самом деле согласен с самим собой в течение этого промежутка времени ...
Уилл Дин
Одна из задач конструктора - следить за тем, чтобы все переменные состояния были явно инициализированы. Если вы придерживаетесь этого соглашения, вы можете использовать superконструкцию для вызова родительского конструктора; Затем он позаботится об инициализации переменных частного состояния в родительском классе.
ncmathsadist
31

Как правило, если что-то намеренно не считается публичным, я делаю это частным.

Если возникает ситуация, когда мне нужен доступ к этой частной переменной или методу из производного класса, я меняю ее с частной на защищенную.

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

Я бы сказал, что это нормально (и, вероятно, лучший способ сделать это) для большинства разработчиков.

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

Единственные реальные исключения из этого - если вы занимаетесь доставкой двоичных dll в виде черного ящика третьим лицам. В основном это Microsoft, поставщики «Custom DataGrid Control» и, возможно, несколько других крупных приложений, которые поставляются с библиотеками расширяемости. Если вы не относитесь к этой категории, не стоит тратить время / силы на беспокойство о подобных вещах.

Орион Эдвардс
источник
8

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

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

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

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

Например:

  • У меня есть класс прямоугольника, ширина и высота которого хранятся как защищенные переменные.
  • Очевидным подклассом (в моем контексте) является класс «DisplayedRectangle», единственное отличие которого состоит в том, что я ограничиваю ширину и высоту допустимыми значениями для графического отображения.
  • Но сейчас это невозможно , поскольку мой класс DisplayedRectangle не может по- настоящему ограничивать эти значения, поскольку любой его подкласс может напрямую переопределять значения, при этом все еще обрабатываясь как DisplayedRectangle.

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

deworde
источник
7

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

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

  1. лениво создавайте значение «на лету», когда свойство читается. Если вы добавите защищенный метод получения, вы можете лениво создать значение и передать его обратно.

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

  3. Установите точку останова или добавьте вывод отладки при чтении или записи переменной.

  4. Переименуйте эту переменную-член, не просматривая весь код, который может ее использовать.

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

Майкл Бишоп
источник
7

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

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

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

Методы аксессора / мутатора предлагают значительно большую стабильность API и гибкость реализации при обслуживании.

Кроме того, если вы сторонник объектно-ориентированного подхода, объекты взаимодействуют / взаимодействуют посредством отправки сообщений, а не чтения / настройки состояния.

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

Richj
источник
4

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

Но у меня есть один хороший пример: допустим, у вас есть некий универсальный контейнер. Он имеет внутреннюю реализацию и внутренние средства доступа. Но вам нужно предложить как минимум 3 публичных доступа к своим данным: map, hash_map, vector-like. Тогда у вас будет что-то вроде:

template <typename T, typename TContainer>
class Base
{
   // etc.
   protected
   TContainer container ;
}

template <typename Key, typename T>
class DerivedMap     : public Base<T, std::map<Key, T> >      { /* etc. */ }

template <typename Key, typename T>
class DerivedHashMap : public Base<T, std::hash_map<Key, T> > { /* etc. */ }

template <typename T>
class DerivedVector  : public Base<T, std::vector<T> >        { /* etc. */ }

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

Резюме Таким образом, вы защитили данные, используемые производным классом. Тем не менее, мы должны учитывать тот факт, что базовый класс должен быть абстрактным.

паэрцебал
источник
он не нарушает инкапсуляцию больше, чем публичные члены. Это параметр, указывающий, что производные классы могут использовать состояние класса, которое не предоставляется пользователям класса.
gbjbaanb
@gbjbaanb: вы противоречите себе: «это не нарушает инкапсуляцию больше, чем публичные члены» отличается от «[только] производные классы могут использовать состояние класса». «защищенный» - это что-то среднее между публичным и частным. Так что "protected [...] несколько нарушает инкапсуляцию" все еще верно ...
paercebal
Фактически, в языке C ++ адаптеры контейнера, такие как std :: stack, будут предоставлять объект-контейнер с защищенной переменной с именем «c».
Йоханнес Шауб - лит
Я знаю, что этот пост довольно старый, но я чувствую необходимость вмешаться. Вы не "несколько" нарушаете инкапсуляцию, вы полностью ее нарушаете. protectedне более инкапсулирован, чем public. Я хочу доказать свою неправоту. Все, что вам нужно сделать, это написать класс с защищенным членом и запретить мне изменять его. Очевидно, что класс не должен быть окончательным, поскольку весь смысл использования protected - наследование. Либо что-то инкапсулировано, либо нет. Промежуточного состояния нет.
Taekahn
3

Короче да.

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

Джей Страмел
источник
1
И наоборот, частные переменные-члены также никогда не нужны; public достаточно для любого использования.
Алиса
3

Для записи, в пункте 24 «Исключительного C ++» в одной из сносок Саттер говорит: «Вы бы никогда не написали класс, у которого есть общедоступная или защищенная переменная-член. Не так ли? (Независимо от плохого примера, установленного некоторыми библиотеками .) "

ХАККНРОК
источник
2

Для получения подробной информации о модификаторах доступа .Net перейдите сюда

У защищенных переменных-членов нет реальных преимуществ или недостатков, вопрос в том, что вам нужно в вашей конкретной ситуации. Обычно принято объявлять переменные-члены как частные и разрешать внешний доступ через свойства. Кроме того, некоторые инструменты (например, некоторые преобразователи O / R) ожидают, что данные объекта будут представлены свойствами, и не распознают общедоступные или защищенные переменные-члены. Но если вы знаете, что хотите, чтобы ваши подклассы (и ТОЛЬКО ваши подклассы) имели доступ к определенной переменной, нет причин не объявлять ее защищенной.

Ману
источник
Желание подклассов для доступа к переменной сильно отличается от желания, чтобы они могли свободно изменять ее. Это один из основных аргументов против защищенных переменных: теперь ваш базовый класс не может предполагать, что какой-либо из его инвариантов сохраняется, потому что любой производный класс может делать с защищенными членами абсолютно все, что угодно. Это главный аргумент против них. Если им просто нужен доступ к данным, то ... напишите средство доступа. : P (Я использую защищенные переменные, хотя, вероятно, больше, чем следовало бы, и я постараюсь сократить их количество!)
underscore_d