Как не дать производному классу сделать приватную / защищенную виртуальную функцию общедоступной?

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

Редактировать: Судя по вашим ответам и, как я думал ранее, кажется, нет способа предотвратить это. Но так как в этой ситуации легко ошибиться (клиент обязательно касается защищенной виртуальной функции), было бы разумно, чтобы компилятор предупреждал о таком использовании. Я пытался проверить это с 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 ответов

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

С чего бы это? Каждый дизайнер должен решить, какой у него внешний интерфейс.

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

Вам может потребоваться аргумент токена, который может быть создан только производными типами. Конечно, тогда они могли бы просто выставить подкласс токена. Таким образом, вы должны предоставить виртуальный деструктор, а 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 минут моей жизни потрачены впустую...

Другие вопросы по тегам