限制命名空间中字段的变更/访问

4
我希望能够限制一个类的字段的访问(或修改),使其只能通过专用函数/方法从给定的命名空间中访问(或设置)。
由于友元关系不具有传递性或继承性,我的想法是使用内部命名空间(应该是实现细节的命名空间)并将相应的函数设为友元。例如,如果我们想要存储一个秘密,并且只允许内部命名空间中的代码访问它,而其他人则可以设置它,我希望这个做法可以解决问题:
#include <cassert>

namespace detail {

// forward declaration doesn't help
// class SecretHolder;
// int GetSecret(SecretHolder& sh);

class SecretHolder {
  int secret_;
  friend int GetSecret(SecretHolder& sh);
 public:
  void SetSecret(int val) { secret_ = val; }
};

// nor does inlining as "friend void GetSecret(SecretHolder& sh) { ... }"
int GetSecret(SecretHolder& sh) { return sh.secret_; }
void DoNothing() { }

} // namespace detail

class User : public detail::SecretHolder {
  // ...
};

int main(int argc, char* argv[]) {
  User u;
  u.SetSecret(42);
  assert(GetSecret(u) == 42); // XXX: why it works without detail:: ???
  // DoNothing(); // this would indeed give a compile error
  return 0;
}

然而,看起来像是detail::GetSecret在定义它的命名空间detail之外被“逃脱”了,因此上面的程序可以编译并且断言通过。我很困惑,特别是在阅读ISO/IEC 14882:2011的7.3.1.2/3后:

每个在命名空间中首次声明的名称都是该命名空间的成员。如果非局部类中的友元声明首先声明一个类或函数,则友元类或函数是最内层封闭命名空间的成员。未限定查找(3.4.1)或限定查找(3.4.3)直到该命名空间范围内提供了匹配的声明时,才能找到友元的名称。如果调用友元函数,则其名称可能由将函数参数的类型关联到命名空间和类中的函数考虑名称查找(3.4.2)来找到。如果友元声明中的名称既不是限定的也不是模板ID,并且声明是函数或扩展类型说明符,则查找以确定实体是否先前已声明的范围不得考虑最内层封闭命名空间之外的任何作用域。

我使用了三个主要编译器(GCC、CLANG 和 ICC)的最新版本进行了检查,行为似乎是一致的。我在这里缺少什么,如何实现我最初想要的结果?


你想找到一种禁用参数相关查找的方法,我认为。 - user253751
1个回答

0
所以,我没有ISO的副本可以参考我的答案,但是我将尝试解释发生了什么。你的问题不在于友元规则,而在于命名空间中声明类型对象作为参数时定义的函数的可见性。
为了说明我所说的,让我创建另一个函数来看看我们是否可以在不指定命名空间的情况下调用它:
bool SeeYou(SecretHolder * sh)
{
    return sh != nullptr;

}

这些更改使我们能够进行更多的编译时检查:

int _tmain(int argc, _TCHAR * argv[])
{
    User u;
    u.SetSecret(42);

// The following works because GetSecret is being given an object that
// exists within the detail namespace.
    assert(GetSecret(u) == 42);

// Let's try some things with see you.
    assert(SeeYou(&u));

// This fails because nullptr is not of the detail namespace.
    //assert(SeeYou(nullptr));  // Compile fails.
    assert(detail::SeeYou(nullptr);  // Compile succeeds.

// Let's see what an object of type SecretHolder does.
    detail::SecretHolder * secretSecretsAreNoFun = new SecretHolder();
    assert(CaughtYou(secretSecretsAreNoFun)); // This works.
    secretSecretsAreNoFun = nullptr;
    assert(CaughtYou(secretSecretsAreNoFun)); // Throws an assert.        

    //assert(CaughtYou(nullptr)); // Compilation fails.

    return 0;
}

编辑:

我忘了提到,对于你问题的第二部分,如何实现私有密钥,我没有一个好的答案,除了避免继承包含我的秘密密钥的类。变量secret_将始终位于User对象堆栈上的特定偏移量。

我建议您以一种每个命名空间内的人都可以查找到对象和密钥的方式进行映射。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接