убрать C++ детализированный эквивалентный друг? (Ответ: Идиома адвоката-клиента)

Почему 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, который я затем считал лучшим общеизвестным решением. (Для не хлопания меня по спине слишком трудно...), я также изменил подпись 'ограниченных' для демонстрации передачи параметра. Общая стоимость этой идиомы является одним классом и одним другом объявление на набор полномочий, один друг, объявление на набор утвердило вызывающую сторону и одну передающую обертку на выставленный метод на набор полномочий. Большая часть лучшего обсуждения ниже вращается вокруг передающего шаблона вызова, которого очень похожая 'Ключевая' идиома избегает за счет менее прямой защиты.

47
задан Community 23 May 2017 в 11:54
поделиться

4 ответа

Идиома Attorney-Client может быть тем, что вы ищете. Механика не слишком отличается от вашего решения с прокси-классом члена, но этот способ более идиоматичен.

17
ответ дан 26 November 2019 в 19:42
поделиться

Существует очень простой шаблон, который активно получил название 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 () {}}; полностью отключает конструктор копирования и, таким образом, работает только в варианте делегирования (предотвращая сохранение экземпляра).
  • K-Ballo , за указание на то, как C ++ 11 улучшил ситуацию с другом T;
62
ответ дан 26 November 2019 в 19:42
поделиться

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

class X {
  class SomewhatPrivate {
    friend class YProxy1;

    void restricted();
  };

public:
  ...

  SomewhatPrivate &get_somewhat_private_parts() {
    return priv_;
  }

private:
  int n_;
  SomewhatPrivate priv_;
};

НО:

  1. Я не думаю, что это стоит усилий.
  2. Необходимость использования ключевого слова friend может указывать на то, что ваш дизайн ошибочен, возможно, есть способ сделать то, что вам нужно, без него. Я стараюсь избегать этого, но если это делает код более читаемым, поддерживаемым или снижает потребность в стандартном коде, я использую его.

РЕДАКТИРОВАТЬ: Для меня приведенный выше код (обычно) мерзость, которую (обычно) следует использовать , а не .

0
ответ дан 26 November 2019 в 19:42
поделиться

Вы можете использовать шаблон, описанный в книге Джеффа Олджера «C ++ для настоящих программистов». У него нет специального названия, но там его называют «драгоценные камни и грани». Основная идея заключается в следующем: среди вашего основного класса, который содержит всю логику, вы определяете несколько интерфейсов (не настоящих интерфейсов, как они), которые реализуют подчасти этой логики. Каждый из этих интерфейсов (аспект в терминах книги) обеспечивает доступ к некоторой логике основного класса (драгоценного камня). Кроме того, каждый фасет содержит указатель на экземпляр драгоценного камня.

Что это значит для вас?

  1. Вы можете везде использовать любую грань вместо драгоценного камня.
  2. Пользователи фасетов не должны знать о структуре драгоценного камня, так как она может быть объявлена ​​вперед и использована через PIMPL-паттерн.
  3. Другие классы могут относиться к фасету, а не к драгоценному камню - это ответ на ваш вопрос о том, как предоставить ограниченное количество методов указанному классу.

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

РЕДАКТИРОВАТЬ: Вот код:

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 ++, и это труд, но, может быть, слишком многословный ...

2
ответ дан 26 November 2019 в 19:42
поделиться
Другие вопросы по тегам:

Похожие вопросы: