【问题标题】:c++ using declaration, scope and access controlc++ 使用声明、范围和访问控制
【发布时间】:2011-01-06 06:54:53
【问题描述】:

'using' 声明通常用于将基类的一些成员函数引入范围,否则这些成员函数将被隐藏。从这个角度来看,它只是一种使可访问信息更方便使用的机制。
但是:'using' 声明也可用于更改访问约束(不仅适用于函数,还适用于属性)。例如:

class C{
public:
  int a;
  void g(){ cout << "C:g()\n"; }
  C() : a(0){}
};

class D : public C{
private:
  using C::a;
  using C::g;
public:
  D() { a = 1; }
};

int main(void){
  D d;
  cout << d.a << endl;  //error: a is inaccessible
  C *cp = &d;
  cout << cp->a << endl; //works
  d.g();  //error: g is inaccessible
  cp->g();  //works
  return 0;
}

我认为派生类中的这种访问限制实际上是没有用的,因为您始终可以从指向基类的指针访问 g() 和 a。那么至少不应该有某种编译器警告吗?或者禁止派生类对访问的这种限制不是更好吗? using 声明不是添加访问约束的唯一可能性。也可以通过覆盖基类的函数并将其放置在具有更多访问约束的部分中来完成。 是否有一些合理的例子表明以这种方式限制访问确实是必要的?如果不是,我不明白为什么应该允许它。

还有一件事:至少对于 g++,相同的代码在没有“使用”这个词的情况下也能很好地编译。这意味着对于上面的示例:可以编写 C::a;和 C::g;而不是使用 C::a;使用 C::g;第一个只是后者的捷径还是有一些细微的差别?

//编辑:
所以从下面的讨论和答案中,我的结论是:
- 允许使用公共继承限制派生类中的访问约束
- 有一些有用的例子可以使用它
- 它的使用可能会导致与模板组合出现问题(例如,派生类不再是某些模板类/函数的有效参数,尽管它是基础)
- 更简洁的语言设计不应该允许这样的使用
- 编译器至少可以发出某种警告

【问题讨论】:

    标签: c++ scope access-control using-declaration


    【解决方案1】:

    关于没有using 的声明:这些被称为“访问声明”,已被弃用。这是来自标准的文本,来自11.3/1

    可以通过在派生类中提及其限定 ID 来更改基类成员的访问权限 派生类声明。这种提及称为访问声明。访问声明 qualified-id; 的效果被定义为等同于声明 usingqualified-id; [脚注:不推荐使用访问声明;成员 using-declarations (7.3.3) 提供了一种更好的方法来做同样的事情。在 C++ 语言的早期版本中,访问声明受到更多限制。它们被概括并等同于 using-declarations - 结束脚注]

    我会说,在派生类中将 public 成员更改为 privateprotected 成员通常是不好的,因为这会违反替换原则:你知道基类有一些函数,如果你强制转换为派生类,那么你期望这些函数也是可调用的,因为派生类is-a 基础。就像你已经提到的那样,无论如何,这个不变量已经被允许转换(隐式工作!)到基类引用或限定函数名,然后调用(然后是公共)函数的语言强制执行。

    如果你想禁止某人调用一组基础函数,那么我认为这暗示包含(或在极少数情况下,私有继承)是一个更好的主意。

    【讨论】:

    • 不是去挑答案,而是在其中引入一些讨论:是否真的违反了替换原则?在多态的动态版本中它不是。在derived 对象中传递时,适用于base 引用或指针的代码也将起作用,因为它将隐式向上转换为base。一旦您将模板和静态多态性添加到组合中,情况就不同了。确实,设计用于base 参数的模板不适用于derived 对象。但是限制访问的全部目的可能是(这是一个不能在那里使用的base)?
    • 替换原则意味着,无论何时您期望一个基类型的对象(例如指针、参数传递等),您也可以传递一个“更好”类型的对象——并且派生出更好的 C++ 类型具有公共基础的类。对于非公共继承,由于访问限制,不再允许替换。所以我认为将公共成员更改为私有并不违反公共继承中的替换原则,因为您根本没有真正限制访问约束(始终可从基础获得)。 @dribeas:你能多解释一下模板吗?
    • “当你期望一个类型为 base 的对象时,你也可以传递一个更好类型的对象”——但这在这里被违反了。想象一下,您的迭代器继承了std::iterator,然后继承了private: using iterator::value_type; - 它使用标准算法失败:您的类将无法再替换迭代器,因为没有公共value_type 成员。正如 dribeas 所说,当您仅检查动态多态性时,它不会违反。但是当模板到位时,这变得可以观察到。
    • 好的,你说得对——但这仍然是替代品吗?在处理模板时,会在编译时生成该模板的具体实例,并且不再有全局函数接受某个基类的参数。我认为标准算法也是模板?如果你尝试在你的类中使用其中一些算法,这应该会在编译时失败。
    • 只在编译时检查访问,所以如果有问题,它会在编译时。关于它是否是替代......我确实认为它是,只是不同。模板化算法与动态语言中的等效算法(想想python)没有什么不同:真正的要求是接口提供。如果您阅读标准(直到概念有第二次机会进入其中),STL 算法的模板参数具有标准定义接口的名称:ForwardIteratorRandomAccessIterator,因此所有迭代器都以某种方式派生自这些。跨度>
    【解决方案2】:

    虽然您展示的 using 声明确实提供了一种更改访问级别(但仅降低)的机制,但这并不是它在这种情况下的主要用途。那里的使用上下文主要是为了允许访问由于语言机制而从基类中隐藏的函数。例如

    class A {
    public:
       void A();
       void B();
    };
    
    class B {
    public:
       using A::B;
       void B(int); //This would shadow A::B if not for a using declaration
    };
    

    【讨论】:

    • 我认为他已经知道:“通常,'using' 声明用于将基类的一些成员函数引入范围,否则这些成员函数将被隐藏。”。对我来说,这听起来像是他在专门询问访问级别问题。
    【解决方案3】:

    声明

    using C::a
    

    将“a”引入本地命名范围,以便您以后可以使用“a”来引用“C::a”;既然如此,“C::a”和“a”是可以互换的,只要你不声明一个名为“a”的局部变量。

    声明不会改变访问权限;您只能在子类中访问“a”,因为“a”不是私有的。

    【讨论】:

      猜你喜欢
      • 2022-01-21
      • 2012-01-10
      • 2012-06-28
      • 2020-04-10
      • 1970-01-01
      • 2011-07-29
      • 2020-03-18
      • 2014-09-20
      • 1970-01-01
      相关资源
      最近更新 更多