Как не дать производному классу сделать приватную / защищенную виртуальную функцию общедоступной? - PullRequest
5 голосов
/ 27 января 2010

Существуют веские причины для создания интерфейса базового класса со всеми виртуальными функциями как частными или защищенными (см. this ). Но тогда как предотвратить создание производных классов (которые могут быть в руках внешних клиентов) частной виртуальной функции как общедоступной? В Virtually Yours авторы говорят об этой проблеме, но решение не обсуждается.

Редактировать : Судя по вашим ответам и, как я думал ранее, предотвратить это невозможно. Но, поскольку в этой ситуации легко ошибиться (клиент обязательно касается защищенной виртуальной функции), было бы разумно, чтобы компилятор предупреждал о таком использовании. Я пытался проверить это с g ++. Сначала я написал:

class A {
        protected:
        virtual void none() { return; }
};

class B: public A {
        public:
        void none() { return; }
};

g++ -c -Wall -pedantic file.cpp скомпилировано без ошибок. Добавление -Weffc++ дало предупреждение: warning: ‘class A’ has virtual functions and accessible non-virtual destructor, что имеет смысл. После добавления виртуального деструктора предупреждений нет. Таким образом, нет никакого предупреждения для этого легкого к ошибкам случая.

Ответы [ 6 ]

12 голосов
/ 27 января 2010

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

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

Редактировать: Это вовсе не «аргумент» - он просто указывает на основуна котором были приняты решения.Поскольку у меня есть моя копия D & E от ответа на предыдущий вопрос, я напишу немного больше, если она здесь 1 :

Это более важночтобы разрешить полезную функцию, чем предотвратить любое неправильное использование: Вы можете писать плохие программы на любом языке.Важно минимизировать вероятность случайного неправильного использования функций, и много усилий было потрачено на то, чтобы поведение по умолчанию конструкций C ++ было разумным или приводило к ошибкам во время компиляции.Например, по умолчанию проверяются все типы аргументов функций - даже через отдельные границы компиляции - и по умолчанию все члены класса являются закрытыми.Однако язык системного программирования не может помешать определенному программисту сломать систему, поэтому усилия по проектированию лучше тратить на обеспечение средств для написания хороших программ, чем на предотвращение неизбежных плохих.В долгосрочной перспективе программисты, кажется, учатся.Это вариант старого лозунга C "Доверяй программисту".Существуют различные правила проверки типов и контроля доступа, которые позволяют провайдеру класса четко указывать, что ожидается от пользователей, для защиты от несчастных случаев.Эти правила не предназначены для защиты от умышленного нарушения (§2.10).

В §2.10 он, среди прочего, говорит:

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

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

1 §4.3, стр.115, 116.

2 голосов
/ 26 декабря 2011

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

С чего бы это? Проектировщик каждого класса должен принять решение о его внешнем интерфейсе.

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

2 голосов
/ 27 января 2010

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

2 голосов
/ 27 января 2010

Контроль доступа в C ++, возможно, не делает то, что вы хотите. Он не предназначен для применения ограничений в стиле DRM, чтобы помешать вам делиться своим доступом. Если A имеет доступ к B, то A может вызвать B и использовать результат для любых целей, в том числе вернуть его другому абоненту, который не имеет доступа к B.

Проблема, которая обсуждается в статье, на которую вы ссылаетесь, не связана с намеренным или злонамеренным разделением B. А это то, что происходит, если вы поместите общедоступную виртуальную функцию в опубликованный интерфейс, а затем попытаетесь изменить класс таким образом, чтобы он использует предложенные шаблоны шаблонов, включая частные виртуальные функции. Дочерние классы имеют открытые переопределения виртуальной функции, поэтому вы больше не можете разделять две проблемы (доступ и виртуальность) без изменения всех дочерних классов. То, как я это прочитал, статья действительно предлагает решение проблемы, которую она представляет, и это решение «никогда не делайте виртуальные функции общедоступными в первую очередь».

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

Причина, по которой это не решает вашу проблему, в том, что они не рассмотрели вашу проблему.

2 голосов
/ 27 января 2010

Вы не можете. «Виртуальность» функции и тип доступа - это две разные несвязанные концепции.

1 голос
/ 27 января 2010

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

protected:
class ProtectedToken { virtual ~ProtectedToken() { } };
virtual void my_tough_cookie(int arg,
  ProtectedToken const &tok = ProtectedToken() ) {
    assert ( typeid( tok ) == typeid( ProtectedToken ) );
    …
}

Конечно, это нехорошо для всех, в том числе для себя.

Редактировать: Бах, это не работает. Даже если это произойдет, вы можете сделать public: using Base::ProtectedToken и победить защиту таким образом. Еще 15 минут моей жизни впустую ...

...