Дружба не передается по наследству - какие есть альтернативы?
Я написал / пишу фрагмент кода для анализа физики, первоначально для себя, который теперь, надеюсь, будет использоваться и расширяться небольшой группой физиков. Никто из нас не является гуру 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
...
Обратите внимание, что этот вид разбиения позволяет размещать инструмент и структурный код в отдельных библиотеках - вы даже можете ограничить доступ к структурному коду отдельно для кода инструмента и просто поделиться заголовками и скомпилированной библиотекой.