为什么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); }
};
我远不是一个软件组织大师,但感觉就像界面简单和最小特权原则在这方面的语言直接相悖。一个更清楚的例子,我的愿望可能是一个Person
类,声明的方法,如takePill(Medicine *)
tellTheTruth()
和forfeitDollars(unsigned int)
,只有Physician
,Judge
或TaxMan
实例/成员方法,分别应该考虑调用。每个主要界面方面需要一次性代理或接口类对我不好,但如果你知道我错过了什么,请说出来。
答案来自Drew Hall:Dr Dobbs - Friendship and the Attorney-Client Idiom
上面的代码最初称为包装类'Proxy'而不是'Attorney',并使用指针代替引用,但在其他方面等同于Drew发现的,我认为这是最常见的解决方案。 (不要太苛刻地背拍自己......)我还改变了'restricted'的签名来演示参数转发。这个习语的总成本是每个权限集一个类和一个朋友声明,每个集合批准的一个朋友声明,每个权限集每个暴露方法一个转发包装器。以下大多数更好的讨论围绕着转发呼叫样板,一个非常相似的“密钥”习语避免以较少的直接保护为代价。
Attorney-Client idiom可能是您正在寻找的。机制与您的成员代理类解决方案没有太大区别,但这种方式更具惯用性。
有一个非常简单的模式,后来被称为PassKey,这是very easy in 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>
Foo
可以构建或复制Key<Foo>
值得注意的是:
Foo
的类不能构造或复制Key<Foo>
,因为友谊不是传递性的Foo
本身无法传递Key<Foo>
任何人打电话给Bar::special
,因为调用它不仅需要持有一个实例,而是制作副本因为C ++是C ++,所以要避免一些问题:
public
public
= default
将允许聚合初始化绕过手动用户定义的默认构造函数(从而允许任何类型获取实例)这非常微妙,我曾建议你复制/粘贴Key
的上述定义,而不是试图从内存中重现它。
允许委派的变体:
class Bar { public: void special(int a, Key<Foo> const&); };
在这个变体中,任何拥有Key<Foo>
实例的人都可以调用Bar::special
,所以即使只有Foo
可以创建Key<Foo>
,它也可以将凭据传播给可信赖的副官。
在此变体中,为了避免流氓中尉泄露密钥,可以完全删除复制构造函数,这允许将密钥生存期与特定词法范围捆绑在一起。
在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() {} };
完全禁用复制构造函数的注释中指向我,因此仅在委托变体中起作用(阻止存储实例)。friend T;
的情况我知道这是一个老问题,但问题仍然存在。虽然我喜欢Attorney-Client习惯用法的想法,但我希望为已被授予私有(或受保护)访问权限的客户端类提供透明接口。
我想象已经完成了与此类似的事情,但粗略地看一下并没有发现任何事情。以下方法(C ++ 11 up)基于每个类(不是每个对象)工作,并使用“私有类”使用的CRTP基类来公开公共函子。只有那些具有特定访问权限的类才能调用仿函数的operator(),然后通过存储的引用直接调用相关的私有方法。
没有函数调用开销,唯一的内存开销是每个需要曝光的私有方法的一个引用。该系统用途广泛;允许任何函数签名和返回类型,就像调用私有类中的虚函数一样。
对我来说,主要的好处是语法之一。虽然在私有类中需要一个公认的相当丑陋的仿函数对象声明,但这对客户端类是完全透明的。以下是原始问题的示例:
struct Doctor; struct Judge; struct TaxMan; struct TheState;
struct Medicine {} meds;
class Person : private GranularPrivacy<Person>
{
private:
int32_t money_;
void _takePill (Medicine *meds) {std::cout << "yum..."<<std::endl;}
std::string _tellTruth () {return "will do";}
int32_t _payDollars (uint32_t amount) {money_ -= amount; return money_;}
public:
Person () : takePill (*this), tellTruth (*this), payDollars(*this) {}
Signature <void, Medicine *>
::Function <&Person::_takePill>
::Allow <Doctor, TheState> takePill;
Signature <std::string>
::Function <&Person::_tellTruth>
::Allow <Judge, TheState> tellTruth;
Signature <int32_t, uint32_t>
::Function <&Person::_payDollars>
::Allow <TaxMan, TheState> payDollars;
};
struct Doctor
{
Doctor (Person &patient)
{
patient.takePill(&meds);
// std::cout << patient.tellTruth(); //Not allowed
}
};
struct Judge
{
Judge (Person &defendant)
{
// defendant.payDollars (20); //Not allowed
std::cout << defendant.tellTruth() <<std::endl;
}
};
struct TheState
{
TheState (Person &citizen) //Can access everything!
{
citizen.takePill(&meds);
std::cout << citizen.tellTruth()<<std::endl;
citizen.payDollars(50000);
};
};
GranularPrivacy基类通过定义3个嵌套模板类来工作。第一个,'Signature',将函数返回类型和函数签名作为模板参数,并将它们转发给functor的operator()方法和第二个nest模板类'Function'。这是通过指向Host类的私有成员函数的参数来进行的,该函数必须具有Signature类提供的签名。在实践中,使用了两个单独的“函数”类;这里给出的是另一个用于const函数的,为简洁起见省略了。
最后,Allow类使用variadic模板机制递归地从显式实例化的基类继承,具体取决于在其模板参数列表中指定的类的数量。 Allow的每个继承级别都有一个来自模板列表的朋友,而using语句将继承层次结构的基类构造函数和operator()带入最派生的范围。
template <class Host> class GranularPrivacy
{
friend Host;
template <typename ReturnType, typename ...Args> class Signature
{
friend Host;
typedef ReturnType (Host::*FunctionPtr) (Args... args);
template <FunctionPtr function> class Function
{
friend Host;
template <class ...Friends> class Allow
{
Host &host_;
protected:
Allow (Host &host) : host_ (host) {}
ReturnType operator () (Args... args) {return (host_.*function)(args...);}
};
template <class Friend, class ...Friends>
class Allow <Friend, Friends...> : public Allow <Friends...>
{
friend Friend;
friend Host;
protected:
using Allow <Friends...>::Allow;
using Allow <Friends...>::operator ();
};
};
};
};
我希望有人觉得这很有用,任何意见或建议都会受到欢迎。这肯定还在进行中 - 我特别想将Signature和Function类合并到一个模板类中,但一直在努力寻找一种方法来实现这一点。更多完整,可运行的例子可以在cpp.sh/6ev45和cpp.sh/2rtrj找到。
您可以使用Jeff Aldger的book'C ++中为真正的程序员'描述的模式。它没有特别的名称,但它被称为“宝石和刻面”。基本思想如下:在包含所有逻辑的主类中,您定义了几个实现该逻辑子部分的接口(不是真正的接口,就像它们一样)。这些接口中的每一个(就书的方面而言)提供了对主类(gemstone)的一些逻辑的访问。此外,每个facet都包含指向gemstone实例的指针。
这对你意味着什么?
希望这可以帮助。如果你愿意,我可以在这里发布代码示例,以更清楚地说明这种模式。
编辑:这是代码:
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;
}
EDIT2:你的类Foo1可能更复杂,例如,它包含另外两种方法:
void Foo1::DoAnotherThing()
{
cout << “Foo::DoAnotherThing()” << endl;
}
void Foo1::AndYetAnother()
{
cout << “Foo::AndYetAnother()” << endl;
}
他们可以通过class 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。那些facet类可以在不同的地方使用,并且他们的调用者不会意识到Foo1的实现。也许这不是你真正想要的,但是你想要的东西对于C ++来说是不可能的,这是一个令人工作的工作,但可能过于冗长......
类似于下面的代码,您可以通过friend
关键字对您公开的私有州的哪些部分进行细粒度控制。
class X {
class SomewhatPrivate {
friend class YProxy1;
void restricted();
};
public:
...
SomewhatPrivate &get_somewhat_private_parts() {
return priv_;
}
private:
int n_;
SomewhatPrivate priv_;
};
但:
friend
关键字的需要可能表明您的设计存在缺陷,也许有一种方法可以在没有它的情况下完成您的需求。我试图避免它,但如果它使代码更易读,可维护或减少对样板代码的需要我使用它。编辑:对我来说,上面的代码(通常)是一个应该(通常)不被使用的憎恶。
我对Matthieu M.给出的解决方案进行了一些小的改进。他的解决方案的局限在于你只能授予对单个类的访问权限。如果我想让三个班级中的任何一个有权访问该怎么办?
#include <type_traits>
#include <utility>
struct force_non_aggregate {};
template<typename... Ts>
struct restrict_access_to : private force_non_aggregate {
template<typename T, typename = typename std::enable_if<(... or std::is_same<std::decay_t<T>, std::decay_t<Ts>>{})>::type>
constexpr restrict_access_to(restrict_access_to<T>) noexcept {}
restrict_access_to() = delete;
restrict_access_to(restrict_access_to const &) = delete;
restrict_access_to(restrict_access_to &&) = delete;
};
template<typename T>
struct access_requester;
template<typename T>
struct restrict_access_to<T> : private force_non_aggregate {
private:
friend T;
friend access_requester<T>;
restrict_access_to() = default;
restrict_access_to(restrict_access_to const &) = default;
restrict_access_to(restrict_access_to &&) = default;
};
// This intermediate class gives us nice names for both sides of the access
template<typename T>
struct access_requester {
static constexpr auto request_access_as = restrict_access_to<T>{};
};
template<typename T>
constexpr auto const & request_access_as = access_requester<T>::request_access_as;
struct S;
struct T;
auto f(restrict_access_to<S, T>) {}
auto g(restrict_access_to<S> x) {
static_cast<void>(x);
// f(x); // Does not compile
}
struct S {
S() {
g(request_access_as<S>);
g({});
f(request_access_as<S>);
// f(request_access_as<T>); // Does not compile
// f({request_access_as<T>}); // Does not compile
}
};
struct T {
T() {
f({request_access_as<T>});
// g({request_access_as<T>}); // Does not compile
// g({}); // Does not compile
}
};
这使用稍微不同的方法来使对象不是聚合。我们有一个空的私有基类,而不是用户提供的构造函数。在实践中,它可能无关紧要,但这意味着此实现是一个POD类,因为它仍然是微不足道的。然而,效果应该保持不变,因为无论如何都没有人会存储这些物体。