Есть нет функции, которая контролирует видимость / доступность класса в C ++.
Есть ли способ подделать это?
Есть ли какой-нибудь макрос / шаблон / магия C ++, которая может имитировать ближайшее поведение?
util.h (библиотека)
class Util{
//note: by design, this Util is useful only for B and C
//Other classes should not even see "Util"public: static void calculate(); //implementation in Util.cpp
};
B.h (библиотека)
#include "Util.h"class B{ /* ... complex thing */ };
C.h (библиотека)
#include "Util.h"class C{ /* ... complex thing */ };
D.h (Пользователь)
#include "B.h" //<--- Purpose of #include is to access "B", but not "Util"class D{
public: static void a(){
Util::calculate(); //<--- should compile error
//When ctrl+space, I should not see "Util" as a choice.
}
};
Сделать всех членов Util
чтобы быть частным, тогда объявите:
friend class B;
friend class C;
(Редактировать: Поблагодарить A.S.H для «здесь не требуется предварительное объявление».)
Недостаток :-
Util
как-то распознать B
а также C
,Util
сломай любой private
охранник доступа.D
просто не могу использовать Util
, но все еще могу это увидеть.Util
все еще выбор при использовании автозаполнения (например, Ctrl + Space) в D.h
, (Изменить) Примечание: Все дело в удобстве для кодирования; для предотвращения ошибок или неправильного использования / лучшего автозаполнения / лучшей инкапсуляции. Речь идет не о противодействии взлому или предотвращении несанкционированного доступа к функции.
(Изменить, принято):
К сожалению, я могу принять только одно решение, поэтому я субъективно выбрал тот, который требует меньше работы и обеспечивает большую гибкость.
Для будущих читателей, Приет Кукрети (& texasbruce в комментарии) и Шмуэль Х. (& A.S.H Это комментарий) также предоставил хорошие решения, которые стоит прочитать.
Одним из возможных решений было бы засунуть Util
в пространство имен, и typedef
это внутри B
а также C
классы:
namespace util_namespace {
class Util{
public:
static void calculate(); //implementation in Util.cpp
};
};
class B {
typedef util_namespace::Util Util;
public:
void foo()
{
Util::calculate(); // Works
}
};
class C {
typedef util_namespace::Util Util;
public:
void foo()
{
Util::calculate(); // Works
}
};
class D {
public:
void foo()
{
Util::calculate(); // This will fail.
}
};
Если Util
класс реализован в util.cpp
, это потребует обернуть его внутри namespace util_namespace { ... }
, Так далеко как B
а также C
обеспокоены тем, что их реализация может ссылаться на класс с именем Util
и никто не станет мудрее. Без включения typedef
, D
не найдет класс с таким именем.
Я думаю, что лучший способ не включать Util.h
в публичном заголовке вообще.
Чтобы сделать это, #include "Util.h"
только в реализации cpp
файл:
Lib.cpp
:
#include "Util.h"
void A::publicFunction()
{
Util::calculate();
}
Делая это, вы убедитесь, что изменение Util.h
будет иметь значение только в ваших файлах библиотеки, а не в пользователях библиотеки.
Проблема с этим подходом заключается в том, что не сможет использовать Util
в ваших публичных заголовках (A.h
, B.h
). Форвард-объявление может быть частичным решением этой проблемы:
// Forward declare Util:
class Util;
class A {
private:
// OK;
Util *mUtil;
// ill-formed: Util is an incomplete type
Util mUtil;
}
Один из способов сделать это — создать единый класс-посредник, единственная цель которого — предоставить интерфейс доступа к базовым функциям. Это требует немного шаблонного. затем A
а также B
являются подклассами и, следовательно, могут использовать интерфейс доступа, но не напрямую Utils
:
class Util
{
private:
// private everything.
static int utilFunc1(int arg) { return arg + 1; }
static int utilFunc2(int arg) { return arg + 2; }
friend class UtilAccess;
};
class UtilAccess
{
protected:
int doUtilFunc1(int arg) { return Util::utilFunc1(arg); }
int doUtilFunc2(int arg) { return Util::utilFunc2(arg); }
};
class A : private UtilAccess
{
public:
int doA(int arg) { return doUtilFunc1(arg); }
};
class B : private UtilAccess
{
public:
int doB(int arg) { return doUtilFunc2(arg); }
};
int main()
{
A a;
const int x = a.doA(0); // 1
B b;
const int y = b.doB(0); // 2
return 0;
}
ни A
или же B
иметь доступ к Util
непосредственно. Код клиента не может позвонить UtilAccess
участники через A
или же B
экземпляры либо. Добавление дополнительного класса C
который использует текущий Util
функциональность не потребует модификации Util
или же UtilAccess
код.
Это означает, что у вас есть более жесткий контроль над Util
(особенно если это состояние), что облегчает рассуждение кода, поскольку весь доступ осуществляется через заданный интерфейс, а не дает прямой / случайный доступ к анонимному коду (например, A
а также B
).
Это требует шаблона и не распространяет автоматически изменения от Util
Однако это более безопасный образец, чем прямая дружба.
Если вы не хотите иметь подкласс, и вы счастливы иметь UtilAccess
изменить для каждого класса использования, вы можете внести следующие изменения:
class UtilAccess
{
protected:
static int doUtilFunc1(int arg) { return Util::utilFunc1(arg); }
static int doUtilFunc2(int arg) { return Util::utilFunc2(arg); }
friend class A;
friend class B;
};
class A
{
public:
int doA(int arg) { return UtilAccess::doUtilFunc1(arg); }
};
class B
{
public:
int doB(int arg) { return UtilAccess::doUtilFunc2(arg); }
};
Есть также некоторые связанные решения (для более строгого контроля доступа к частям класса), одно из которых называется Адвокат-клиент а другой называется отмычка, оба обсуждаются в этом ответе: чистый C ++ гранулированный эквивалент друга? (Ответ: идиома адвокат-клиент) . Оглядываясь назад, я думаю, что решение, которое я представил, представляет собой вариант идиомы «адвокат-клиент».