为什么c++有这个规则:显式实例化定义忽略成员访问说明符:参数类型和返回类型可能是私有的

余国良*_*余国良 1 c++ c++11 c++17

根据这个规则,C++可以在安全语法下直接破坏封装

时间:2018-12-07 标签:c++11

#include <iostream>

class A {
 public:
  A() = default;
 private:
  int data_ = 0;
};

template < int A::*Member >
class Access {
 public:
   friend  int GetPrivateData(A& obj) {
     return obj.*Member;
  }
};

template  class Access<&A::data_>; // explicit instantiation

int GetPrivateData(A& );


int main() {
  A obj;
  GetPrivateData(obj);

  return 0;
}

//https://www.zhihu.com/question/521898260/answer/2876618819
Run Code Online (Sandbox Code Playgroud)

时间:2017-12-17 标签:c++17

class A {
 public:
  A(int num) : data_(num) {};
 private:
  int data_ = 0;
};

template <typename PtrType>
class Access {
 public:
 inline static PtrType ptr;
};

template <auto T>
struct PtrTaker {
    struct Transferer {
        Transferer() {
            Access<decltype(T)>::ptr = T;
        }
    };
    inline static Transferer tr;
};

template class PtrTaker<&A::data_>; // explicit instantiation

int main() {
  A a{10};

  int b = a.*Access<int A::*>::ptr;
  
  return 0;
}

//https://www.zhihu.com/question/521898260/answer/2876618819
Run Code Online (Sandbox Code Playgroud)

这两种实现原理略有不同。前者可以看作是利用链接漏洞,但后者确实是完全标准的实现。

而且这个语法无法通过简单的关键字搜索来检查,因为会有很多正常的显式实例化来混淆。

use*_*522 7

您所指的行为在[temp.spec.general]/6中指定

这个规则自 C++98 以来一直存在,因此自 C++ 的第一次标准化以来就存在。对于 C++17 来说这并不是什么新鲜事。

规则也是必要的。假设您想使用Access<&A::data_> inside A,whereA::data_可访问,但您不希望Access<&A::data_>被隐式实例化,而只想在单个翻译单元中显式实例化。由于显式实例化声明不能出现在A的作用域内,因此不可能对可访问性进行任何检查。这将使这样的显式实例化完全不可能。

这同样适用于显式专业化和部分专业化。

您可以使用其他技术(例如友元注入)来访问私有成员,这不是问题,因为这些不会意外发生。如果用户决定访问某个private成员,那么无论如何都没有什么可以阻止他们。(例如,他们可以简单地friend在类定义中添加自己的类/函数作为 a ,这本身可能会或可能不会违反 ODR,但在实践中不会产生负面后果。)

访问控制的目的只是为了使用户不会意外地使用他们不应该直接使用的成员,并且仅仅使用私有成员实例化作为参数不允许访问私有成员。这需要额外的有针对性的工作。如果用户完成所有工作来访问私有成员,那么该用户就是故意破坏该类的预期用途,如果结果是一个损坏的程序,那么他们将承担责任。