Дружба не передается по наследству - какие есть альтернативы?

Я написал / пишу фрагмент кода для анализа физики, первоначально для себя, который теперь, надеюсь, будет использоваться и расширяться небольшой группой физиков. Никто из нас не является гуру C++. Я собрал небольшую структуру, которая абстрагирует данные о "физическом событии" в объекты, на которые воздействует цепочка инструментов, которые можно легко менять и вставлять в зависимости от требований анализа.

Это создало две половины кода: код "физический анализ", который манипулирует объектами события и производит наши результаты через производные базового "инструмента"; и "структурный" код, который присоединяет входные файлы, разбивает задание на параллельные запуски, связывает инструменты в цепочку согласно некоторому сценарию и т. д.

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

Я хотел бы иметь возможность:

  • A) продемонстрировать очевидным образом, что структурный код никак не редактирует данные события
  • Б) применить это, как только другие пользователи начнут расширять код сами (никто из нас не является экспертом, и физика всегда на первом месте - перевод: все, что не было решено, является честной игрой противного хака)

В моем идеальном сценарии данные о событиях были бы конфиденциальными, а производные физические инструменты наследовали доступ от базового класса Tool. Конечно, в действительности это не разрешено. Я слышал, что для этого есть веские причины, но это не проблема.

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

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


Для прояснения ситуации у меня есть что-то вроде

class Event
{
    // The event data (particle collections etc)
};

class Tool
{
    public:
        virtual bool apply(Event* ev) = 0;
};

class ExampleTool : public Tool
{
    public:
        bool apply(Event* ev)
        {
            // do something like loop over the electron collection
            // and throw away those will low energy
        }
};

Идеальным было бы ограничить доступ к содержимому события только этими инструментами по двум причинам (A и B), указанным выше.

Спасибо всем за предложенные решения. Я думаю, что, как я и подозревал, идеальное решение, о котором я мечтал, невозможно. Решение dribeas было бы идеальным в любой другой ситуации, но именно в функции apply() код должен быть как можно более четким и лаконичным, поскольку мы будем в основном тратить весь день на написание / редактирование функций apply(), а также нужно понимать каждую строчку из них написанную каждым из остальных. Дело не столько в способностях, сколько в удобочитаемости и усилиях. Мне нравится препроцессорное решение от "Бесполезного". Это на самом деле не навязывает разделение, но кто-то должен быть действительно злым, чтобы сломать его. Для тех, кто предложил библиотеку, я думаю, что это, безусловно, будет хорошим первым шагом, но на самом деле не решает две основные проблемы (так как мне все равно нужно будет предоставить источник).

3 ответа

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

В C++ есть три квалификатора доступа: public, protected а также private, Предложение с производными физическими инструментами, наследующими доступ от базового класса Tool, указывает на то, что вы хотите protected доступ, но не ясно, являются ли фактические данные, которые private в Tool (и поэтому protected достаточно) или в настоящее время private в классе, который дружит Tool,

В первом случае просто сделайте данные protected:

class Tool {
protected:
   type data;
};

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

class Data {
   type this_is_private;
   friend class Tool;
};
class Tool {
protected:
   static type& gain_acces_to_data( Data& d ) { 
       return d.this_is_private;
   }
};
class OneTool : public Tool {
public:
   void foo( Data& d ) {
      operate_on( gain_access_to_data(d) );      
   }
};

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

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

struct Evil : Tool {
   static type& break_rule( Data & d ) {
      return gain_access_to_data( d );
   }
};

И теперь каждый может просто использовать Evil как дверь в Data, Я рекомендую вам прочитать C++FAQ-lite для более глубокого понимания C++.

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

-- ToolInterface.hpp --
class Event; // just forward declare it

class ToolStructuralInterface
{
    // only what the structural code needs to invoke tools
    virtual void invoke(std::list<Event*> &) = 0;
};

-- ToolImplementation.hpp --
class Event
{
    // only the tool code sees this header
};
// if you really want to prevent accidental inclusion in the structural code
#define TOOL_PRIVATE_VISIBILITY

-- StructuralImplementation.hpp --
...
#ifdef TOOL_PRIVATE_VISIBILITY
#error "someone leaked tool implementation details into the structural code"
#endif
...

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

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