干净的 C++细粒度的朋友相当于? (答案:律师-委托人习语)

发布于 2024-09-08 13:15:54 字数 1853 浏览 5 评论 0原文

为什么 C++ 具有任何人都可以调用的 public 成员以及将所有 private 成员公开给 friend 声明给出了外部类或方法,但没有提供向给定调用者公开特定成员的语法?

我想用一些例程来表达接口,这些例程只能由已知的调用者调用,而不必授予这些调用者对所有私有变量的完全访问权限,这感觉像是一个合理的事情。到目前为止,我自己能想到的最好的(如下)和其他人的建议都围绕着不同间接性的习语/模式,我真的只是想要一种方法来拥有单一,明确指示的简单类定义哪些调用者(比我的孩子绝对任何人更精细)可以访问哪些成员。表达以下概念的最佳方式是什么?

// 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) ,只有 PhysicianJudgeTaxMan 实例/成员方法才应该考虑调用。每个主要接口方面都需要一次性代理或接口类,这让我感到不舒服,但如果您知道我遗漏了一些东西,请大声说出来。

Drew Hall接受的答案:Dr Dobbs - 友谊和律师-客户习惯用法

上面的代码最初将包装类称为“代理”而不是“律师”,并使用指针而不是引用,但后来否则相当于德鲁发现的,然后我认为这是最好的众所周知的解决方案。 (不要太用力地夸奖自己......)我还更改了“restricted”的签名来演示参数转发。此习惯用法的总体成本是每个权限集一个类和一个朋友声明、每个经过批准的调用者集一个朋友声明以及每个权限集每个公开方法一个转发包装器。下面大部分更好的讨论都围绕着转发调用样板,一个非常相似的“Key”习惯用法以较少的直接保护为代价避免了该样板。

Why does C++ have public members that anyone can call and friend declarations that expose all private members to given foreign classes or methods but offer no syntax to expose particular members to given callers?

I want to express interfaces with some routines to be invoked only by known callers without having to give those callers complete access to all privates, which feels like a reasonable thing to want. The best I could come up with myself (below) and suggestions by others so far revolve around idioms/pattern of varying indirectness, where I really just want a way to have single, simple class definitions that explicitly indicate what callers (more granularly than me, my children, or absolutely anybody) can access which members. What is the best way to express the concept below?

// 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); }
};

I'm nowhere near being a software organization guru, but it feels like interface simplicity and the principle of least privilege are directly at odds in this aspect of the language. A clearer example for my desire might be a Person class with declared methods like takePill(Medicine *) tellTheTruth() and forfeitDollars(unsigned int) that only Physician, Judge, or TaxMan instances/member methods, respectively, should even consider invoking. Needing one-time proxy or interface classes for each major interface aspect sits ill with me, but please speak up if you know I'm missing something.

Answer accepted from Drew Hall: Dr Dobbs - Friendship and the Attorney-Client Idiom

The code above originally called the wrapper class 'Proxy' instead of 'Attorney' and used pointers instead of references but was otherwise equivalent to what Drew found, which I then deemed the best generally known solution. (Not to pat myself on the back too hard...) I also changed the signature of 'restricted' to demonstrate parameter forwarding. The overall cost of this idiom is one class and one friend declaration per permission set, one friend declaration per set approved caller, and one forwarding wrapper per exposed method per permission set. Most of the better discussion below revolves around the forwarding call boilerplate that a very similar 'Key' idiom avoids at the expense of less direct protection.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

享受孤独 2024-09-15 13:15:54

有一个非常简单的模式,被追溯称为 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++,所以

需要避免一些问题:

  • 复制构造函数必须是用户定义的,否则默认情况下它是 public 默认
  • 构造函数必须是用户定义的已定义,否则默认为 public 默认
  • 构造函数必须手动定义,因为 = default 将允许聚合初始化绕过手动用户- 定义的默认构造函数(从而允许任何类型获取实例)

这非常微妙,我建议您逐字复制/粘贴上面的 Key 定义,而不是尝试重现它从记忆中。


允许委托的变体:

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 语法也不可用。


特别感谢多年来帮助改进这个答案的人:

  • Luc Touraille,在评论中指出我class KeyFoo: boost::noncopyable { 朋友类 Foo; KeyFoo() {} }; 完全禁用复制构造函数,因此仅适用于委托变体(防止存储实例)。
  • K-ballo,指出 C+ 如何+11 改善了与朋友 T 的情况;

There is a very simple pattern, which has retro-actively been dubbed PassKey, and which is very easy in C++11:

template <typename T>
class Key { friend T; Key() {} Key(Key const&) {} };

And with that:

class Foo;

class Bar { public: void special(int a, Key<Foo>); };

And the call site, in any Foo method, looks like:

Bar().special(1, {});

Note: if you are stuck in C++03, skip to the end of the post.

The code is deceptively simple, it embeds a few key points that are worth elaborating.

The crux of the pattern is that:

  • calling Bar::special requires copying a Key<Foo> in the context of the caller
  • only Foo can construct or copy a Key<Foo>

It is notable that:

  • classes derived from Foo cannot construct or copy Key<Foo> because friendship is not transitive
  • Foo itself cannot hand down a Key<Foo> for anyone to call Bar::special because calling it requires not just holding on to an instance, but making a copy

Because C++ is C++, there are a few gotchas to avoid:

  • the copy constructor has to be user-defined, otherwise it is public by default
  • the default constructor has to be user-defined, otherwise it is public by default
  • the default constructor has to be manually defined, because = default would allow aggregate initialization to bypass the manual user-defined default constructor (and thus allow any type to get an instance)

This is subtle enough that, for once, I advise you to copy/paste the above definition of Key verbatim rather than attempting to reproduce it from memory.


A variation allowing delegation:

class Bar { public: void special(int a, Key<Foo> const&); };

In this variant, anyone having an instance of Key<Foo> can call Bar::special, so even though only Foo can create a Key<Foo>, it can then disseminate the credentials to trusted lieutenants.

In this variant, to avoid a rogue lieutenant leaking the key, it is possible to delete the copy constructor entirely, which allows tying the key lifetime to a particular lexical scope.


And in C++03?

Well, the idea is similar, except that friend T; is not a thing, so one has to create a new key type for each holder:

class KeyFoo { friend class Foo; KeyFoo () {} KeyFoo (KeyFoo const&) {} };

class Bar { public: void special(int a, KeyFoo); };

The pattern is repetitive enough that it might be worth a macro to avoid typos.

Aggregate initialization is not an issue, but then again the = default syntax is not available either.


Special thanks to people who helped improving this answer over the years:

  • Luc Touraille, for pointing to me in the comments that class KeyFoo: boost::noncopyable { friend class Foo; KeyFoo() {} }; completely disables the copy constructor and thus only works in the delegation variant (preventing storing instance).
  • K-ballo, for pointing out how C++11 improved the situation with friend T;
世界和平 2024-09-15 13:15:54

律师-客户惯用语可能就是您正在寻找的。该机制与您的成员代理类解决方案没有太大不同,但这种方式更惯用。

The Attorney-Client idiom may be what you're looking for. The mechanics are not too different from your member proxy class solution, but this way is more idiomatic.

娜些时光,永不杰束 2024-09-15 13:15:54

我知道这是一个老问题,但问题仍然相关。虽然我喜欢“律师-客户”习惯用法的想法,但我希望为已被授予私有(或受保护)访问权限的客户类提供一个透明的接口。

我想类似的事情已经完成了,但粗略地环顾四周并没有发现任何东西。以下方法 (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”将函数返回类型和函数签名作为模板参数,并将它们转发给函子的operator() 方法和第二个嵌套模板类“Function”。这是通过指向 Host 类的私有成员函数的指针来参数化的,该函数必须具有 Signature 类提供的签名。在实践中,使用两个单独的“函数”类;此处给出的一个,以及 const 函数的另一个,为简洁起见,省略了。

最后,Allow 类使用可变参数模板机制从显式实例化的基类递归继承,具体取决于其模板参数列表中指定的类的数量。 Allow 的每个继承级别都有一个来自模板列表的友元,并且 using 语句将基类构造函数和运算符 () 沿继承层次结构向上带到最派生的范围。

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/6ev45cpp.sh/2rtrj

I know this is an old question, but the problem is still relevant. While I like the idea of the Attorney-Client idiom, I wanted a transparent interface for client classes that had been granted private (or protected) access.

I imagine something similar to this has been done already, but a cursory look around didn't turn up anything. The following method (C++11 up) works on a per class (not per object) basis and uses a CRTP base class that is used by the 'private class' to expose a public functor. Only those classes that have specifically been given access can call the functor's operator(), which then directly invokes the associated private method via a stored reference.

There is no function call overhead and the only memory overhead is one reference per private method that requires exposure. The system is very versatile; any function signature and return type is permitted as is calling virtual functions in the private class.

For me, the main benefit is one of syntax. While an admittedly rather ugly declaration of the functor objects is required in the private class, this is completely transparent to the client classes. Here is an example taken from the original question:

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);
    };
};

The GranularPrivacy base class works by defining 3 nested template classes. The first of these, 'Signature', takes the function return type and function signature as template parameters, and forwards these to both the functor's operator() method and the the second nest template class, 'Function'. This is parametrized by a pointer to a private member function of the Host class, which must have the signature provided by the Signature class. In practice, two separate 'Function' classes are used; the one given here, and another for const functions, omitted for brevity.

Finally the Allow class recursively inherits from a explicitly instantiated base class using the variadic template mechanism, depending on the number of classes specified in it's template argument list. Each inheritance level of Allow has one friend from the template list, and the using statements bring the base class constructor and operator () up the inheritance hierarchy into the most derived scope.

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 ();
            };
        };
    };
};

I hope somebody finds this useful, any comments or suggestions would be most welcome. This is definitely still work in progress - I would particularly like to merge the Signature and Function classes into just one template class, but have been struggling to find a way to do this. More complete, runnable examples can be found at cpp.sh/6ev45 and cpp.sh/2rtrj.

七婞 2024-09-15 13:15:54

您可以使用 Jeff Aldger 的“真正程序员的 C++”中描述的模式。它没有特殊的名称,但被称为“宝石和刻面”。基本思想如下:在包含所有逻辑的主类中,定义几个实现该逻辑子部分的接口(不是真正的接口,只是像它们一样)。每个接口(书中的方面)都提供对主类(gemstone)的一些逻辑的访问。此外,每个方面都保存着指向宝石实例的指针。

这对你来说意味着什么?

  1. 您可以在任何地方使用任何刻面而不是宝石。
  2. 刻面的用户不必了解宝石结构,因为可以通过 PIMPL 模式进行前向声明和使用。
  3. 其他类可以引用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; 
} 

编辑2:
您的类 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。这些方面类可以在不同的地方使用,并且它们的调用者不应该知道 Foo1 实现。也许这不是你真正想要的,但你想要的对于 C++ 来说是不可能的,这是一个解决方法,但可能太冗长了......

You can use a pattern described in Jeff Aldger's book 'C++ for real programmers'. It has no special name but there it is referred as 'gemstones and facets'. The basic idea is as following: among your main class that contains all the logic, you define several interfaces (not real interfaces, just like them) that implements sub-parts of that logic. Each of those interface (facet in terms of book) provides access to some of logic of main class (gemstone). Also, each facet holds the pointer to gemstone instance.

What does this mean for you?

  1. You can use any facet everywhere instead of gemstone.
  2. Users of facets doesn't have to know about gemstone structure, as in could be forward-declared and used through PIMPL-pattern.
  3. Other classes can refer to facet rather to gemstone - this is the answer to your question about how to expose limited nubmer of methods to specified class.

Hope this helps. If you want, I could post code samples here to illustrate this pattern more clearly.

EDIT: Here's the code:

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:
Your class Foo1 could be more complex, for example, it contains two another methods:

void Foo1::DoAnotherThing() 
{ 
 cout << “Foo::DoAnotherThing()” << endl; 
} 

void Foo1::AndYetAnother() 
{ 
 cout << “Foo::AndYetAnother()” << endl; 
} 

And they're accessible via 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(); 
    } 

Those methods are not in PFoo1 class, so you cannot access them through it. In this way you can split the behavior of Foo1 to two (or more) facets PFoo1 and PFoo2. Those facets classes could be used in different places, and their caller shoudn't be aware of Foo1 implementation. Maybe it's not what you really want, but what you want is impossible for C++, and this is a work-aroud, but maybe too verbose...

缱倦旧时光 2024-09-15 13:15:54

类似于下面的代码,您可以通过 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 关键字可能表明您的设计存在缺陷,也许有一种方法可以在没有它的情况下完成您需要的操作。我试图避免它,但如果它使代码更具可读性、可维护性或减少对样板代码的需求,我就会使用它。

编辑:对我来说,上面的代码(通常)是令人厌恶的,应该(通常)使用。

Something akin to the code below will allow you fine-grained control over which parts of your private state you make public through the friend keyword.

class X {
  class SomewhatPrivate {
    friend class YProxy1;

    void restricted();
  };

public:
  ...

  SomewhatPrivate &get_somewhat_private_parts() {
    return priv_;
  }

private:
  int n_;
  SomewhatPrivate priv_;
};

BUT:

  1. I don't think it's worth the effort.
  2. The need to use the friend keyword might suggest your design is flawed, perhaps there's a way you can do what you need without it. I try to avoid it but if it makes the code more readable, maintainable or reduces the need for boilerplate code I use it.

EDIT: To me the code above is (usually) an abomination that should (usually) not be used.

时光沙漏 2024-09-15 13:15:54

我对 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 类,因为它仍然很琐碎。然而,效果应该保持不变,因为无论如何没有人会存储这些对象。

I have written a minor improvement to the solution given by Matthieu M. The limitation of his solution is that you can only grant access to a single class. What if I want to let any one of three classes have access?

#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
    }
};

This uses a slightly different approach to making the object not an aggregate. Rather than having a user-provided constructor, we have an empty private base class. In practice, it probably does not matter, but it means that this implementation is a POD class because it remains trivial. The effect should remain the same, however, because no one is going to be storing these objects anyway.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文