Почему C++ имеет public
участники, которых любой может позвонить и friend
объявления, которые выставляют все private
участники к данным внешним классам или методам, но предложению никакой синтаксис для представления конкретных участников данным вызывающим сторонам?
Я хочу выразить интерфейсы некоторыми стандартными программами, которые будут вызваны только известными вызывающими сторонами, не имея необходимость предоставлять тем вызывающим сторонам полный доступ ко всем рядовым, который чувствует себя подобно разумной вещи хотеть. Лучшие, которые я мог придумать сам (ниже) и предложения другими до сих пор, вращаются вокруг идиом/шаблона переменной косвенности, где я действительно просто хочу способ иметь единственные, простые определения классов, которые явно указывают на то, к чему вызывающие стороны (более детализировано, чем я, мои дети или абсолютно кто-либо) могут получить доступ который участники. Что лучший способ состоит в том, чтобы выразить понятие ниже?
// Can I grant Y::usesX(...) selective X::restricted(...) access more cleanly?
void Y::usesX(int n, X *x, int m) {
X::AttorneyY::restricted(*x, n);
}
struct X {
class AttorneyY; // Proxies restricted state to part or all of Y.
private:
void restricted(int); // Something preferably selectively available.
friend class AttorneyY; // Give trusted member class private access.
int personal_; // Truly private state ...
};
// Single abstract permission. Can add more friends or forwards.
class X::AttorneyY {
friend void Y::usesX(int, X *, int);
inline static void restricted(X &x, int n) { x.restricted(n); }
};
Я нигде не близок быть организационным гуру программного обеспечения, но такое чувство, что интерфейсная простота и принцип наименьшего количества полномочия непосредственно противоречат в этом аспекте языка. Более ясным примером для моего требования мог бы быть a Person
класс с заявленными методами как takePill(Medicine *)
tellTheTruth()
и forfeitDollars(unsigned int)
то единственное Physician
, Judge
, или TaxMan
методы экземпляров/участника, соответственно, должны даже рассмотреть вызов. Необходимость в одноразовых или интерфейсных классах прокси для каждого главного интерфейсного аспекта находится плохая мной, но говорите, если Вы знаете, что я пропускаю что-то.
Ответ принял от Drew Hall: доктор Dobbs - Дружба и Идиома Адвоката-клиента
Код выше первоначально названного класс обертки 'Прокси' вместо 'Адвоката' и используемые указатели вместо ссылок, но был в других отношениях эквивалентен тому, что нашел Drew, который я затем считал лучшим общеизвестным решением. (Для не хлопания меня по спине слишком трудно...), я также изменил подпись 'ограниченных' для демонстрации передачи параметра. Общая стоимость этой идиомы является одним классом и одним другом объявление на набор полномочий, один друг, объявление на набор утвердило вызывающую сторону и одну передающую обертку на выставленный метод на набор полномочий. Большая часть лучшего обсуждения ниже вращается вокруг передающего шаблона вызова, которого очень похожая 'Ключевая' идиома избегает за счет менее прямой защиты.
Идиома Attorney-Client может быть тем, что вы ищете. Механика не слишком отличается от вашего решения с прокси-классом члена, но этот способ более идиоматичен.
Существует очень простой шаблон, который активно получил название PassKey , и который очень прост в C ++ 11 :
template <typename T>
class Key { friend T; Key() {} Key(Key const&) {} };
И с этим:
class Foo;
class Bar { public: void special(int a, Key<Foo>); };
А сайт вызова в любом методе Foo
выглядит так:
Bar().special(1, {});
Примечание: если вы застряли на C ++ 03, перейдите к концу сообщения.
Код обманчиво прост, он включает несколько ключевых моментов, которые стоит проработать.
Суть шаблона заключается в следующем:
Bar :: special
требует копирования Key
только в контексте вызывающего ] Foo
может создавать или копировать Key
Примечательно, что:
Foo
, не могут создавать или копировать Key
, потому что дружба не транзитивна Сам Foo
не может передать Key
кому-либо, чтобы вызвать Bar :: special
, потому что для его вызова требуется не просто удерживать на экземпляр, но сделать копию Поскольку C ++ - это C ++, есть несколько ошибок, которых следует избегать:
общедоступный
по умолчанию общедоступный
по умолчанию = default
позволит инициализировать агрегат Чтобы обойти вручную определяемый пользователем конструктор по умолчанию (и, таким образом, позволить любому типу получить экземпляр) Это достаточно тонко, на этот раз я советую вам скопировать / вставить приведенное выше определение ключа
дословно, а не пытаться воспроизвести его по памяти.
Вариант, разрешающий делегирование:
class Bar { public: void special(int a, Key<Foo> const&); };
В этом варианте любой, у кого есть экземпляр Key
, может вызвать Bar :: special
, так что даже если только Foo
может создать Key
, а затем распространить учетные данные среди доверенных лиц.
В этом варианте, чтобы избежать утечки ключа злоумышленником, можно полностью удалить конструктор копирования, что позволяет привязать время жизни ключа к определенной лексической области видимости.
А в C ++ 03?
Идея аналогична, за исключением того, что friend T;
- это не вещь, поэтому нужно создать новый тип ключа для каждого держателя:
class KeyFoo { friend class Foo; KeyFoo () {} KeyFoo (KeyFoo const&) {} };
class Bar { public: void special(int a, KeyFoo); };
Шаблон достаточно повторяющийся, поэтому, возможно, стоит добавить макрос, чтобы избежать опечаток.
Агрегированная инициализация не является проблемой, но опять же синтаксис = default
также недоступен.
Особая благодарность людям, которые помогли улучшить этот ответ на протяжении многих лет:
class KeyFoo: boost :: noncopyable {friend class Foo; KeyFoo () {}};
полностью отключает конструктор копирования и, таким образом, работает только в варианте делегирования (предотвращая сохранение экземпляра). другом T;
Нечто подобное приведенному ниже коду позволит вам детально контролировать, какие части вашего частного состояния вы публикуете с помощью ключевого слова friend
.
class X {
class SomewhatPrivate {
friend class YProxy1;
void restricted();
};
public:
...
SomewhatPrivate &get_somewhat_private_parts() {
return priv_;
}
private:
int n_;
SomewhatPrivate priv_;
};
НО:
friend
может указывать на то, что ваш дизайн ошибочен, возможно, есть способ сделать то, что вам нужно, без него. Я стараюсь избегать этого, но если это делает код более читаемым, поддерживаемым или снижает потребность в стандартном коде, я использую его. РЕДАКТИРОВАТЬ: Для меня приведенный выше код (обычно) мерзость, которую (обычно) следует использовать , а не .
Вы можете использовать шаблон, описанный в книге Джеффа Олджера «C ++ для настоящих программистов». У него нет специального названия, но там его называют «драгоценные камни и грани». Основная идея заключается в следующем: среди вашего основного класса, который содержит всю логику, вы определяете несколько интерфейсов (не настоящих интерфейсов, как они), которые реализуют подчасти этой логики. Каждый из этих интерфейсов (аспект в терминах книги) обеспечивает доступ к некоторой логике основного класса (драгоценного камня). Кроме того, каждый фасет содержит указатель на экземпляр драгоценного камня.
Что это значит для вас?
Надеюсь, это поможет. Если хотите, я могу разместить здесь образцы кода, чтобы более четко проиллюстрировать этот шаблон.
РЕДАКТИРОВАТЬ: Вот код:
class Foo1; // This is all the client knows about Foo1
class PFoo1 {
private:
Foo1* foo;
public:
PFoo1();
PFoo1(const PFoo1& pf);
~PFoo();
PFoo1& operator=(const PFoo1& pf);
void DoSomething();
void DoSomethingElse();
};
class Foo1 {
friend class PFoo1;
protected:
Foo1();
public:
void DoSomething();
void DoSomethingElse();
};
PFoo1::PFoo1() : foo(new Foo1)
{}
PFoo1::PFoo(const PFoo1& pf) : foo(new Foo1(*(pf
{}
PFoo1::~PFoo()
{
delete foo;
}
PFoo1& PFoo1::operator=(const PFoo1& pf)
{
if (this != &pf) {
delete foo;
foo = new Foo1(*(pf.foo));
}
return *this;
}
void PFoo1::DoSomething()
{
foo->DoSomething();
}
void PFoo1::DoSomethingElse()
{
foo->DoSomethingElse();
}
Foo1::Foo1()
{
}
void Foo1::DoSomething()
{
cout << “Foo::DoSomething()” << endl;
}
void Foo1::DoSomethingElse()
{
cout << “Foo::DoSomethingElse()” << endl;
}
РЕДАКТИРОВАТЬ2: Ваш класс Foo1 может быть более сложным, например, он содержит еще два метода:
void Foo1::DoAnotherThing()
{
cout << “Foo::DoAnotherThing()” << endl;
}
void Foo1::AndYetAnother()
{
cout << “Foo::AndYetAnother()” << endl;
}
И они доступны через класс PFoo2
class PFoo2 {
private:
Foo1* foo;
public:
PFoo2();
PFoo2(const PFoo1& pf);
~PFoo();
PFoo2& operator=(const PFoo2& pf);
void DoAnotherThing();
void AndYetAnother();
};
void PFoo1::DoAnotherThing()
{
foo->DoAnotherThing();
}
void PFoo1::AndYetAnother()
{
foo->AndYetAnother();
}
Эти методы не входят в класс PFoo1
, поэтому вы не может получить к ним доступ через него. Таким образом, вы можете разделить поведение Foo1
на два (или более) фасета PFoo1 и PFoo2. Эти классы фасетов можно использовать в разных местах, и их вызывающий не должен знать о реализации Foo1. Может быть, это не то, что вы действительно хотите, но то, что вы хотите, невозможно для C ++, и это труд, но, может быть, слишком многословный ...