为啥受保护的构造函数会在此代码中引发错误?

Posted

技术标签:

【中文标题】为啥受保护的构造函数会在此代码中引发错误?【英文标题】:Why is protected constructor raising an error this this code?为什么受保护的构造函数会在此代码中引发错误? 【发布时间】:2011-01-24 11:59:25 【问题描述】:

关于受保护构造函数的一个问题。我了解到受保护的构造函数可以在派生类中使用。但是,我发现下面的代码有错误。为什么会这样?

class A

    protected:
        A()
;

class B: public A 
    public:
        B() 
            A* f=new A();           // Why it is not working here
        
;

【问题讨论】:

通常发布错误消息会有所帮助:-)。 请注意,Clang 3.0 接受此代码。 GCC 4.6 和 4.7 没有。 @KristianSpangsege 这是 Clang 中的一个错误,在最近的版本中已修复 【参考方案1】:

这与构造函数无关。这就是protected 访问的工作原理。

protected 访问说明符的工作方式,它允许派生类B 访问基类A 对象的内容只有当类A 的对象是类的子对象时B。这意味着您在代码中唯一能做的就是访问A 的内容通过 B:您可以通过B * 类型的指针访问A 的成员(或B & 类型的引用)。但是您不能通过A * 类型的指针(或引用A &)访问相同的成员。

考虑以下示例

class A 
protected:
  int i;
;

class B : A  
  void foo() 
    i = 0;        // OK
    this->i = 0;  // OK

    B *pb = this;
    pb->i = 0;    // OK

    A *pa = this;
    pa->i = 0;    // ERROR

    ((A *) this)->i = 0; // ERROR
  
;

在上面的B::foo 中,您可以使用简单的i 语法访问基本成员A::i。这相当于使用this->i 语法。两者都可以工作,因为指针 this 的类型为 B *,即您正在通过 B * 类型的指针访问 A::i。这正是protected 访问说明符应该允许的。通过pb 指针访问也是出于同样的原因。

但是,当您将 this 指针“转换”为类型 A * 时,您将无法再通过该新指针访问 A::i,即使您仍在尝试访问它们与以前相同的成员。

当应用于构造函数时,protected 访问说明符具有非常特殊的作用:受保护的构造函数只能用于初始化基类子对象。它不能用于初始化独立对象(这是您尝试做的)。换句话说,受保护的构造函数是在 C++ 中实现 抽象类 概念的另一种方式(以及纯虚方法)。如果您的类的构造函数受到保护,那么您的类实际上是抽象。您不能使用它来“从外部”定义独立对象。 (当然,以上不适用于朋友,也不适用于班级本身)。

【讨论】:

当然是+1。但是请注意,有一种方法可以通过使用成员指针:***.com/questions/75538/hidden-features-of-c/… 从B 的范围内访问A 的受保护非静态成员。它很脏,但 100% 标准 :) 我对这种行为的有效性存有疑问,因为我没有找到准确的措辞来说明A 的构造函数是如何进行访问的。事实上,我宁愿发现它应该被允许的迹象——12/2 说“[示例:声明构造函数受保护确保只有派生类和朋友可以使用它创建对象。]”。当然,这仍然可能只适用于子对象而不适用于 A 类型的完整对象,但如果有一个严格(和规范)的规则,我会很高兴。 事实上,C++0x 中关于限制非静态函数或数据成员的受保护成员访问的文本已经更改(n3035 中的11.5)。它现在显然假设存在不使用限定 ID 的访问的类成员访问表达式。构造函数被隐式调用,没有类成员访问表达式。在我看来,要么存在缺陷,要么标准打算在 B 的范围内使 new A 格式良好(尽管我觉得它非常丑陋)。 (其实c++03的文字甚至不允许new AA的范围内:))。 我刚刚在comp.std.c++ 上提出了一个关于构造函数/析构函数访问检查的各种问题。该帖子应在模组接受后立即出现。 (codepad.org/oGQbQeT5) 需要注意的是,Clang 接受 skydoor 的代码。显然,Clang 开发人员对标准的解释与 GCC 不同。【参考方案2】:

当基类具有受保护的构造函数时,您不能直接实例化该类。但是你可以这样做从基类构造函数中调用构造函数:

class A 

protected:
   A() 
;

class B: public A 
public:
   B() : A() // allowed to access constructor like this
   
      A* f = new A(); // Not allowed to access constructor like this!
   
;

如下所示直接调用构造函数会在 gcc 版本 4.1.2 中出现以下错误:

      A* f = new A(); // Not allowed to access constructor like this!

test.cpp:4: error: A::A() is protected

但是,你对构造函数的这个调用没有给出错误:

   B() : A() // allowed to access constructor like this

这背后的原因是第二次调用通过继承访问A()构造函数,这是允许的。但是,这会尝试通过直接调用构造函数来显式创建 A() 的新实例:

      A* f = new A(); // Not allowed to access constructor like this!

这可能看起来不直观,因为 B 应该能够访问 A 的构造函数,因为 B 继承自 A。但是,如果您在 C++ 中声明构造函数 protected,则无法创建该构造函数的实例类,除非通过继承或朋友关系。

【讨论】:

@James - 虽然您通常是正确的,但有两种方法可以创建具有受保护构造函数的类的实例。 1) 在同一类的成员函数内。 2) 在friend@R Samuel Klatchko - 很好,我添加了关于朋友的注释。【参考方案3】:

让我分步回答:

1) 构造函数不会被继承,这就是为什么在派生类中,它们不能被覆盖。 2) 构造函数被调用而不被调用。 3) 如果你在 A 中声明了一个简单的函数,比如 protected void print(),然后尝试在 B 中调用它,它就会起作用。这发生在 bcoz 中,B 继承了这个函数。

4) 当您执行类似 b : a() 的操作时,您正在调用构造函数,这是允许的。 5) 尝试让 B 成为 A 的朋友类,然后运行看看它是否有效。

希望这会有所帮助。

【讨论】:

re 3) 不太准确,这不是因为B 继承了该函数,而是因为它通过B&B* 访问它。如果你有 void B::print2(A& a) a.print(); 那不起作用【参考方案4】:

我有同样的问题,this link 让我清楚。

cppreference 是这样说的:

Protected members form the interface for the derived classes (which is   

distinct from the public interface of the class). 

A protected member of a class Base can only be accessed 

1) by the members and friends of Base 

2) by the members and friends (until C++17) of any class derived from Base, but only when operating on an object of a type that is derived from Base (including this)

【讨论】:

以上是关于为啥受保护的构造函数会在此代码中引发错误?的主要内容,如果未能解决你的问题,请参考以下文章

为啥不能将继承的受保护构造函数公开?

C++:为啥我的 DerivedClass 的构造函数无法访问 BaseClass 的受保护字段?

为啥我不能访问派生构造函数的成员初始化列表中继承的受保护字段?

为啥这里的 String 构造函数应该被保护而不是私有?

Symfony 2,未定义的变量,在构造函数中初始化为 ArrayCollection 的受保护成员通过错误,它是未定义的

派生类中的静态方法可以在 C++ 中调用受保护的构造函数吗?