Unexpected const reference behavior

允我心安 提交于 2019-12-08 19:14:19

问题


#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; 
}

In GCC 4.2, I get this message:

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

If I remove the "private" from B, I get the output I expect:

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

My question is: why does making a method which isn't called private change whether this code compiles? Is this standard-mandated? Is there a workaround?


回答1:


The important verbiage in the current standard (C++03) seems to be in §8.5.3, which explains how references are initialized (In these quotes, T1 is the type of the reference being initialized and T2 is the type of the initializer expression).

If the initializer expression is an rvalue, with T2 a class type, and "cv1 T1" is reference-compatible with "cv2 T2," the reference is bound in one of the following ways (the choice is implementation-defined):

-- The reference is bound to the object represented by the rvalue (see 3.10) or to a sub-object within that object.

-- A temporary of type "cv1 T2" [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. The reference is bound to the temporary or to a sub-object within the temporary.

The constructor that would be used to make the copy shall be callable whether or not the copy is actually done.

So, even if the implementation binds the reference directly to the temporary object, the copy constructor must be accessible.

Note that this is changed in C++0x, per the resolution of CWG defect 391. The new language reads (N3092 §8.5.3):

Otherwise, if T2 is a class type and

-- the initializer expression is an rvalue and "cv1 T1" is reference-compatible with "cv2 T2,"

-- T1 is not reference-related to T2 and the initializer expression can be implicitly converted to an rvalue of type "cv3 T3" (this conversion is selected by enumerating the applicable conversion functions (13.3.1.6) and choosing the best one through overload resolution (13.3)),

then the reference is bound to the initializer expression rvalue in the first case and to the object that is the result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object).

The first case applies and the reference is "bound directly" to the initializer expression.




回答2:


So what you're using is 'copy-initialization':

8.5/11 Initializers

The form of initialization (using parentheses or =) is generally insignificant, but does matter when the entity being initialized has a class type; see below. ...

The initialization that occurs in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and brace-enclosed initializer lists (8.5.1) is called copy-initialization and is equivalent to the form

T x = a;

The initialization that occurs in new expressions (5.3.4), static_cast expressions (5.2.9), functional notation type conversions (5.2.3), and base and member initializers (12.6.2) is called direct-initialization and is equivalent to the form

T x(a);

In 13.3.1.3 "Initialization by constructor", the overloads for the constructor chosen are:

When objects of class type are direct-initialized (8.5), or copy-initialized from an expression of the same or a derived class type (8.5), overload resolution selects the constructor. For direct-initialization, the candidate functions are all the constructors of the class of the object being initialized. For copy initialization, the candidate functions are all the converting constructors (12.3.1) of that class.

So, for copy-initialization, the copy constructor must be available. However, the compiler is permitted to 'optimize away' the copy:

12.2/1 Temporary objects

Even when the creation of the temporary object is avoided (12.8), all the semantic restrictions must be respected as if the temporary object was created. [Example: even if the copy constructor is not called, all the semantic restrictions, such as accessibility (clause 11), shall be satisfied. ]

You can get the effect you want by avoiding copy-initialization and using direct-initialization:

 const A &b(B());  

Note:

Since newer versions of GCC apparently have a different behavior, I thought I'd post this note, which might address the difference (with both behaviors still standards conforming):

8.5.3/5 References says:

If the initializer expression is an rvalue, with T2 a class type, and “cv1 T1” is reference-compatible with “cv2 T2,” the reference is bound in one of the following ways (the choice is implementation-defined):

  • The reference is bound to the object represented by the rvalue (see 3.10) or to a sub-object within that object.

  • A temporary of type “cv1 T2” [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. The reference is bound to the temporary or to a sub-object within the temporary.

The constructor that would be used to make the copy shall be callable whether or not the copy is actually done.

I originally read the last sentence ("the constructor that would be used...") to apply to both options, but maybe it should be read as only applying to the seconds option - or at least maybe that's how the GCC maintainers are reading it.

I'm not sure if this is what's going on between the differing behavior of GCC versions (comments welcome). We're definitely reaching the limits of my language-lawyering skills...




回答3:


I think it is indeed a compiler bug, gcc seems to think that is is copy initialization. Use direct initialization instead:

const A& b(B());

The copy-constructor call in copy initialization is always optimized away (an instance of copy elision), and then doesn't have to be available.



来源:https://stackoverflow.com/questions/3249147/unexpected-const-reference-behavior

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!