限制字段​​变异/访问命名空间

时间:2015-02-25 00:08:30

标签: c++ oop namespaces encapsulation friend

我想限制类字段s.t的访问(或变异)。它只能通过专用的函数/方法从给定的命名空间中访问(或设置)。

由于友谊不是传递或继承的,我的想法是使用内部命名空间(应该是实现细节的命名空间)并将相应的函数作为朋友。例如,如果我们想要存储一个秘密并且只允许内部命名空间内的代码访问它,而其他所有人都要设置它,那么我希望这可以解决这个问题:

#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,因此上面的程序编译并且断言通过。我很困惑,尤其是在我阅读7.3.1.2/3(ISO / IEC 14882:2011)之后:

  

首先在名称空间中声明的每个名称都是其中的一员   命名空间。如果非本地类中的朋友声明首先声明   朋友类或函数所属的类或函数   最里面的封闭命名空间。找不到朋友的名字   非限定查找(3.4.1)或通过限定查找(3.4.3)直到a   在该命名空间范围内提供匹配声明(或者   在授予友谊的类定义之前或之后)。如果是朋友   函数被调用,其名称可以通过名称查找找到   考虑来自名称空间和与之关联的类的函数   函数参数的类型(3.4.2)。如果是朋友的名字   声明既不是合格的,也不是模板ID和声明   是一个函数或一个精心设计的类型说明符,用于确定的查找   该实体是否先前已被宣布不得考虑任何实体   最内层封闭命名空间之外的范围。

我查看了三个主要编译器(GCC,CLANG&amp; ICC)的最新版本,行为似乎是一致的。我在这里错过了什么,以及如何实现我最初的目的?

1 个答案:

答案 0 :(得分:0)

所以,在我的回答中我没有ISO的副本可供参考,但我会对正在发生的事情进行抨击。您的问题不存在于friend的规则中,而是在命名空间中定义的函数的可见性时,它们将命名空间中声明的类型的对象作为参数。

为了说明我在谈论的内容,让我创建另一个函数,看看我们是否可以在不指定命名空间的情况下调用它:

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对象堆栈上的特定偏移处。

我建议您以对象名称中的每个人都可以查找它的方式映射对象和秘密。