2013-04-17 2 views
22

Предположим, у меня есть три класса C++ FooA, FooB и FooC.Разрешение класса «друг» для доступа только к некоторым частным членам

FooA имеет функцию-член с именем Hello, я хочу вызвать эту функцию в классе FooB, но я не хочу, чтобы класс FooC мог его назвать. Лучший способ понять это - объявить FooB как класс друзей FooA. Но до тех пор, пока я это делаю, все Доступны личные и защищенные члены FooA, что совершенно неприемлемо для меня.

Итак, я хочу знать, есть ли какой-либо механизм в C++ (03 или 11) лучше, чем friend класс, который может решить эту дилемму.

И я предполагаю, что это будет хорошо, если следующий синтаксис можно:

class FooA 
{ 
private friend class FooB: 
    void Hello(); 
    void Hello2(); 
private: 
    void Hello3(); 
    int m_iData; 
}; 

class FooB 
{ 
    void fun() 
    { 
     FooA objA; 
     objA.Hello() // right 
     objA.Hello2() // right 
     objA.Hello3() // compile error 
     ojbA.m_iData = 0; // compile error 
    } 
}; 

class FooC 
{ 
    void fun() 
    { 
     FooA objA; 
     objA.Hello() // compile error 
     objA.Hello2() // compile error 
     objA.Hello3() // compile error 
     ojbA.m_iData = 0; // compile error 
    } 
}; 
+1

Нет, нет такой вещи, насколько я знаю. –

+0

@summereno: Так может друг, и они с большей вероятностью это сделают. –

ответ

21

Там нет ничего, чтобы сделать класс другом одной конкретной функции, но вы можете сделать FooB другом «ключ» класс с частным конструктором, а затем введите FooA::Hello этот класс как игнорируемый параметр. FooC будет не в состоянии обеспечить параметр и, следовательно, не может вызвать Hello:

Is this key-oriented access-protection pattern a known idiom?

+1

[Шаблон доступа] (http://stackoverflow.com/q/3324898/500104), yay ~ – Xeo

+1

Спасибо! Я искал его, но не мог вспомнить имя: D –

+0

«Нет ничего, чтобы сделать класс другом одной конкретной функции» звучит неправильно, кстати. 'friend void foo();' работает отлично, но я думаю, вы имеете в виду, что нет возможности напрямую предоставить доступ к одной из ваших функций. – Xeo

14

Я думаю, что вы можете использовать Attorney-Client здесь.

В вашем случае, например, должны быть, как это

class FooA 
{ 
private: 
    void Hello(); 
    void Hello2(); 
    void Hello3(); 
    int m_iData; 

    friend class Client; 
}; 

class Client 
{ 
private: 
    static void Hello(FooA& obj) 
    { 
     obj.Hello(); 
    } 
    static void Hello2(FooA& obj) 
    { 
     obj.Hello2(); 
    } 
    friend class FooB; 
}; 

class FooB 
{ 
    void fun() 
    { 
     FooA objA; 
     Client::Hello(objA); // right 
     Client::Hello2(objA); // right 
     //objA.Hello3() // compile error 
     //ojbA.m_iData = 0; // compile error 
    } 
}; 

class FooC 
{ 
    void fun() 
    { 
     /*FooA objA; 
     objA.Hello() // compile error 
     objA.Hello2() // compile error 
     objA.Hello3() // compile error 
     ojbA.m_iData = 0; // compile error*/ 
    } 
}; 
+0

Этот пример блестящий! – Damian

3

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

Ваш класс FooA не имеет никакого бизнеса, зная о FooB и FooC и «который один должен быть в состоянии использовать его». Он должен иметь открытый интерфейс, и не заботится, который может его использовать. Это точка интерфейса! Вызывающие функции в этом интерфейсе должны всегда оставить FooA в приятном, безопасном, счастливом, стабильном состоянии.

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

Строго говоря, я уверен, что вы можете получить функциональность, с которой вы столкнулись, с каким-то ужасным сложным «шаблоном дизайна», но, честно говоря, я бы не стал беспокоиться.

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

+4

Это было бы правдой, если бы «один класс» был единственной естественной единицей инкапсуляции в C++. Поскольку это не так, этот ответ содержит ряд неточностей. По крайней мере, 'friend' * functions * не являются дробящим оружием, их не существует, чтобы взломать недостатки дизайна, и у классов есть бизнес, знающий о них. Так что * не более * ваша критика должна быть ограничена классами друзей, хотя на самом деле есть также случаи, когда естественная (небедованная, некорректная) единица инкапсуляции представляет собой группу дружественных классов. –

+1

«И если вы обеспокоены тем, что вы случайно можете использовать интерфейс FooA откуда-то, на что вы этого не хотели, просто не делайте этого». - В последнее время я использовал в основном Python, у меня есть много симпатий к этому. Много времени и энергии, потраченных на беспокойство по поводу контроля доступа, можно сохранить, сделав все общедоступным, а затем RTFM, чтобы не вызывать неопубликованные функции. Но это стиль, и люди, которые хотят использовать контроль доступа, находят, что им придется хорошо использовать его, чтобы извлечь из этого какую-либо выгоду. –

+1

@SteveJessop, в то время как я согласен, что ни функции 'friend', ни' friend' не являются * всегда * тупым оружием, они используются как таковые в большинстве случаев. И созвездие класса OP и требование для «ограниченного« друга »явно имеют запах. –

0

Идея friend заключается в том, чтобы разоблачить свой класс для друга.

Есть 2 способа вы могли бы быть более конкретно о том, что вы подвергаете:

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

  2. подружитесь только определенный метод, таким образом только этот метод будет иметь доступ:

.

friend void FooB::fun(); 
1

Самым безопасным решением является использование другого класса, как «сват» для двух классов, вместо того, чтобы один из них friend. Один из способов сделать это предлагается в ответ по @ForEveR, но вы также можете выполнить поиск прокси-классов и других шаблонов проектирования, которые могут применяться.

+0

Я узнал о форме прокси-классов [здесь] (http://stackoverflow.com/questions/994488/what-is-proxy-class-in-c), и это действительно полезно, спасибо! – summereno

0

Вам понадобится наследование. Попробуйте это:

// _ClassA.h 
class _ClassA 
{ 
    friend class ClassA; 
private: 
    //all your private methods here, accessible only from ClassA and _ClassA. 
} 

// ClassA.h 
class ClassA: _ClassA 
{ 
    friend class ClassB; 
private: 
    //all_your_methods 
} 

Таким образом, у вас есть: ClassB является единственным, чтобы иметь возможность использовать ClassA. ClassB не могут получить доступ к _ClassA методам, которые являются частными.

1

Вы можете частично разоблачить интерфейсы класса к указанному клиенту, наследуя его от класса интерфейса.

class FooA_for_FooB 
{ 
public: 
    virtual void Hello() = 0; 
    virtual void Hello2() = 0; 
}; 

class FooA : public FooA_for_FooB 
{ 
private: /* make them private */ 
    void Hello() override; 
    void Hello2() override; 
private: 
    void Hello3(); 
    int m_iData; 
}; 

class FooB 
{ 
    void fun() 
    { 
     FooA objA; 
     FooA_for_FooB &r = objA; 
     r.Hello() // right 
     r.Hello2() // right 
     objA.Hello3() // compile error 
     objA.m_iData = 0; // compile error 
    } 
}; 

class FooC 
{ 
    void fun() 
    { 
     FooA objA; 
     objA.Hello() // compile error 
     objA.Hello2() // compile error 
     objA.Hello3() // compile error 
     objA.m_iData = 0; // compile error 
    } 
}; 

Здесь контроль доступа расширен базовым классом FooA_for_FooB. По ссылке типа FooA_for_FooB, FooB может получить доступ к элементам, определенным в пределах FooA_for_FooB. Однако FooC не может получить доступ к этим элементам, поскольку они были переопределены как частные члены в FooA. Ваша цель может быть достигнута, если вы не используете тип FooA_for_FooB в пределах FooC или в других местах, кроме FooB, которые могут храниться без особого внимания.

Этот подход не требует friend, что делает вещи простыми.

Аналогичную вещь можно сделать, сделав все частным в базовом классе и выборочно обернуть и выставить некоторые из членов как общедоступные в производном классе. Однако этот подход может иногда требовать уродливого понижения. (Потому что базовый класс станет «валютой» среди всей программы.)

Смежные вопросы