Why does c++ have this rule:Explicit instantiation definitions ignore member access specifiers: parameter types and return types may be private

huangapple go评论57阅读模式
英文:

Why does c++ have this rule:Explicit instantiation definitions ignore member access specifiers: parameter types and return types may be private

问题

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

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_>; // 显式实例化

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

  return 0;
}

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_>; // 显式实例化

int main() {
  A a{10};

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

这两种实现原理略有不同。前者可以被视为利用链接漏洞,而后者确实是一个完全标准的实现。

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

英文:

According to this rule, C++ can directly destroy encapsulation under safe syntax

c++11

#include &lt;iostream&gt;

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

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

template  class Access&lt;&amp;A::data_&gt;; // explicit instantiation

int GetPrivateData(A&amp; );


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

  return 0;
}

//https://www.zhihu.com/question/521898260/answer/2876618819

c++17

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

template &lt;typename PtrType&gt;
class Access {
 public:
 inline static PtrType ptr;
};

template &lt;auto T&gt;
struct PtrTaker {
    struct Transferer {
        Transferer() {
            Access&lt;decltype(T)&gt;::ptr = T;
        }
    };
    inline static Transferer tr;
};

template class PtrTaker&lt;&amp;A::data_&gt;; // explicit instantiation

int main() {
  A a{10};

  int b = a.*Access&lt;int A::*&gt;::ptr;
  
  return 0;
}

//https://www.zhihu.com/question/521898260/answer/2876618819

The two implementations have slightly different principles. The former can be regarded as exploiting linking vulnerabilities, but the latter is indeed a completely standard implementation.

And this syntax cannot be checked by a simple keyword search, because there will be a lot of normal explicit instantiation to confuse.

答案1

得分: 6

被您提到的行为在temp.spec.general/6中有明确规定。

这个规则自C++98标准以来就一直存在,因此不是C++17才引入的。

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

相同的规则也适用于显式和部分特化。

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

访问控制的目的只是为了防止用户意外使用他们不应该直接使用的成员,仅仅实例化一个私有成员作为参数并不能访问私有成员。这需要额外的有针对性的工作。如果用户付出了所有这些努力来访问私有成员,那么用户是故意违反了类的预期使用方式,如果结果是一个错误的程序,那就由用户承担责任。

英文:

The behavior you are referring to is specified in [temp.spec.general]/6

This rule has been there since C++98, so since the first standardization of C++. It is not somehow new with C++17.

The rule is also necessary. Suppose you wanted to use Access&lt;&amp;A::data_&gt; inside A, where A::data_ is accessible, but you then don't want Access&lt;&amp;A::data_&gt; to be implicitly instantiated, only explicitly instantiated in a single translation unit. Because the explicit instantiation declaration cannot appear inside A's scope, there can't possibly be any check on accessibility. It would make such an explicit instantiation completely impossible.

The same applies to explicit and partial specializations as well.

That you can use additional techniques such as friend injection to then access the private member is not a problem, since these are not things that can happen accidentally. If the user is determined to access a private member, then there isn't anything stopping them anyway. (They could for example simply add their own class/function as a friend in the class definition, which may or may not be an ODR violation in itself, but will have no negative consequences as such in practice.)

The purpose of access control is only to make it so that the user won't accidentally use a member that they shouldn't use directly and merely instantiating with a private member as argument doesn't allow accessing the private member. That requires additional targeted work. And if a user does all that work to get to the private member, then the user is intentionally breaking the intended use of the class and it will be on them if the result is a broken program.

huangapple
  • 本文由 发表于 2023年2月7日 01:37:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/75364721.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定