Предоставление классу "друг" доступа только к некоторым частным членам
Предположим, что у меня есть три класса С++ FooA, FooB и FooC.
FooA имеет функцию-член с именем Hello
, я хочу вызвать эту функцию в классе FooB, но я не хочу, чтобы класс FooC мог ее вызвать. Лучший способ понять это - объявить FooB как класс друзей FooA. Но пока я это делаю, все частные и защищенные члены FooA будут выставлены, что для меня совершенно неприемлемо.
Итак, я хочу знать, есть ли какой-либо механизм в С++ (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
Нет ничего, чтобы сделать класс другом одной конкретной функции, но вы можете сделать FooB
другом класса "ключ" с частным конструктором, а затем FooA::Hello
взять этот класс в качестве игнорируемого параметра. FooC
не сможет предоставить параметр и, следовательно, не может вызвать Hello
:
Является ли этот ключ-ориентированный шаблон защиты доступа известной идиомой?
Ответ 2
Я думаю, вы можете использовать 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*/
}
};
Ответ 3
Нет, и это не является ограничением. На мой взгляд, ограничение заключается в том, что friend
- дробящее оружие для взлома вокруг недостатков дизайна - существует в первую очередь.
Ваш класс FooA
не знает бизнеса о FooB
и FooC
и "который должен иметь возможность использовать его". У него должен быть открытый интерфейс, и не важно, кто его может использовать. Это точка интерфейса! Функции вызова внутри этого интерфейса всегда должны оставлять FooA
в приятном, безопасном, счастливом, согласованном состоянии.
И если вы обеспокоены тем, что вы случайно можете использовать интерфейс FooA
откуда-то, на что вы этого не хотели, ну просто не делайте этого; С++ не является языком, подходящим для защиты от подобных ошибок пользователя. В этом случае ваше тестовое покрытие должно быть достаточным.
Строго говоря, я уверен, что вы сможете получить функциональность, с которой вы столкнулись, с каким-то ужасным сложным "шаблоном дизайна", но, честно говоря, я бы не стал беспокоиться.
Если это проблема для семантики вашего дизайна программы, я вежливо предлагаю, чтобы ваш дизайн имел недостаток.
Ответ 4
Самое безопасное решение - использовать другой класс как "промежуточный" для ваших двух классов, а не сделать один из них. friend.
Один из способов сделать это предлагается в ответе @ForEveR, но вы можете также можно найти некоторые прокси-классы и другие шаблоны проектирования, которые могут применяться.
Ответ 5
Вы можете частично открыть интерфейсы класса для указанного клиента, наследуя его от класса интерфейса.
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
, что делает вещи простыми.
Аналогичную вещь можно сделать, сделав все частным в базовом классе и выборочно обернув и выставив некоторые из членов как общедоступные в производном классе. Однако этот подход может иногда требовать уродливого понижения. (Потому что базовый класс станет "валютой" среди всей программы.)
Ответ 6
Вся идея friend
заключается в том, чтобы показать свой класс другу.
Есть два способа, которыми вы могли бы быть более конкретными в том, что вы показываете:
-
Наследовать от FooA
, таким образом, открываются только защищенные и общедоступные методы.
-
Подружитесь только с определенным методом, таким образом, только этот метод будет иметь доступ:
.
friend void FooB::fun();
Ответ 7
Вам понадобится наследование. Попробуйте следующее:
// _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
методам, которые являются частными.