代码之家  ›  专栏  ›  技术社区  ›  Zachary Vance

意外的常量引用行为

  •  8
  • Zachary Vance  · 技术社区  · 14 年前
    #include <iostream>
    
    class A { 
      public:  
        A(){ cerr << "A Constructor" << endl; }  
        ~A(){ cerr << "A Destructor" << endl; }  
        A(const A &o){ cerr << "A Copy" << endl; } 
        A& operator=(const A &o){ cerr << "A Assignment" << endl; return *this; }
    };
    
    
    class B : public A { 
      public:  
        B() : A() { cerr << "B Constructor" << endl; }  
        ~B(){ cerr << "B Destructor" << endl; }
      private:
        B(const B &o) : A() { cerr << "B Copy" << endl; } 
        B& operator=(const B &o){ cerr << "B Assignment" << endl; return *this; }
    };
    
    int main() {  
      A a;  
      const A &b = B();  
      return 0; 
    }
    

    在GCC 4.2中,我得到以下信息:

    In function 'int main()':
    Line 16: error: 'B::B(const B&)' is private
    compilation terminated due to -Wfatal-errors.
    

    如果我从B中删除“private”,我将得到预期的输出:

    A Constructor
    A Constructor
    B Constructor
    B Destructor
    A Destructor
    A Destructor
    

    我的问题是:为什么生成一个不被称为private的方法会改变代码是否编译?这个标准是强制性的吗?有解决办法吗?

    3 回复  |  直到 14 年前
        1
  •  4
  •   Community holdenweb    4 年前

    现行标准(C++ 03)中的重要内容似乎是§8.5.3,解释了如何初始化引用(在这些引号中, T1 是要初始化的引用的类型,并且 T2 是初始值设定项表达式的类型)。

    T2级 cv1 T1 cv2 T2 ,“引用以以下方式之一绑定(选项由实现定义):

    --引用被绑定到由r值表示的对象(见3.10)或该对象中的子对象。

    --一种临时的 cv1 T2 创建[sic],并调用构造函数将整个rvalue对象复制到临时对象中。引用绑定到临时对象或临时对象中的子对象。

    无论复制是否真的完成,用于复制的构造函数都应该是可调用的。

    因此,即使实现将引用直接绑定到临时对象,复制构造函数也必须是可访问的。

    CWG defect 391

    否则,如果 T2级 是类类型和

    --初始值设定项表达式是一个右值,并且“ cv1 T1型 “引用是否与” ,"

    -- T1级 引用与 T2级 初始化表达式可以隐式转换为 cv3 T3" (通过列举适用的转换函数(13.3.1.6)并通过过载分辨率(13.3)选择最佳转换函数来选择此转换),

    第一种情况适用,引用“直接绑定”到初始值设定项表达式。

        2
  •  3
  •   Michael Burr    14 年前

    所以你用的是“复制初始化”:

    8.5/11初始化器

    初始化的形式(使用 班级类型;见下文。。。

    参数传递,函数返回, 抛出异常(15.1),处理 大括号内的初始值设定项列表 (8.5.1)称为复制初始化

    T x = a;
    

    在新数据库中发生的初始化 表达式(5.3.4),静态 符号类型转换(5.2.3),以及 基和成员初始值设定项(12.6.2) 相当于形式

    T x(a);
    

    在13.3.1.3“构造函数初始化”中,所选构造函数的重载为:

    当类类型的对象被直接初始化(8.5)或从相同或派生类类型的表达式复制初始化(8.5)时,重载解析将选择构造函数。对于直接初始化,候选函数是被初始化对象的类的所有构造函数。对于复制初始化,候选函数是该类的所有转换构造函数(12.3.1)。

    因此,对于复制初始化,复制构造函数必须可用。但是,允许编译器“优化”副本:

    12.2/1临时物体

    即使避免创建临时对象(12.8),也必须像创建临时对象一样遵守所有语义限制[示例:即使未调用复制构造函数,也应满足所有语义限制,如可访问性(第11条)

    您可以通过避免复制初始化和使用直接初始化来获得所需的效果:

     const A &b(B());  
    

    注:

    由于新版本的GCC显然有不同的行为,我想我应该发布这个注释,它可能会解决这个差异(两种行为仍然符合标准):

    8.5.3/5参考文献指出:

    如果初始值设定项表达式是一个rvalue,T2是一个类类型,并且cv1 T1与cv2 T2是引用兼容的,那么引用将通过以下方式之一绑定(选项由实现定义):

    • 引用绑定到由rvalue表示的对象(见3.10)或其中的子对象 那个物体。

    无论复制是否真的完成,用于复制的构造函数都应该是可调用的。

    我最初读了最后一句话(“将要使用的构造函数…”)来应用于这两个选项,但也许它应该被理解为只应用于seconds选项——或者至少GCC维护人员是这样读的。

    我不确定这是否是GCC版本的不同行为之间发生的事情(欢迎评论)。我们的语言律师技能已经达到极限了。。。

        3
  •  1
  •   Philipp    14 年前

    我认为这确实是一个编译器错误,gcc似乎认为这是复制初始化。改用直接初始化:

    const A& b(B());
    

    复制初始化中的复制构造函数调用总是被优化掉(复制省略的一个实例),然后就不必可用了。