|
Reprinted:
The type T mentioned in this article refers to UDT, not built-in type
Thanks to ilovecpp, all "*Note" parts are added by his reminder
There are three forms to construct an object:
1. T a;
This is nothing to say, call the default ctor to construct a
However, it should be noted that either T has no ctor, and the compiler synthesizes the default ctor, namely T::T()
If T has other forms of ctor added manually, but there is no T::T(), this statement reports an error because the compiler no longer
Synthesize default ctor for T
*Note 1, if there is no default ctor, but all parameters of a ctor have default values, then T a; also holds
2. T a(v);
This statement explicitly uses v as a parameter to call some of the most suitable ctor that can be called with a single parameter to construct a
This statement form is called direct-initialization by the C++ standard
Two points are emphasized here. One is explicit, which means that the ctor that is explicitly declared can be called in this way.
The second point is the most suitable, this is determined by the overload rules, not so taken for granted, the sample code is as follows
struct T
{
T(){}
T(int){}
operator int(){return 0;}
private:
T(T&){}
};
T foo() {return T();}
int main()
{
T a;
T b(a); // Compile error, the most suitable T(T&) is not accessible, it is private
T c(foo());// Compile successfully, the temporary object returned by foo() is rvalue, and cannot be bound to a non-const reference
// Therefore T(T&) is not the most suitable ctor selected by overload rules
// But rvalue can call a custom implicit conversion cast to int and then call T(int) to construct c
// That is, T(int) is selected for overload rules
return 0;
}
This code shows that even if the type is the same, the copy ctor is not necessarily called
If someone suspects that T(T&) is not a copy ctor and thinks that T(const T&) is, please refer to C++ Standard 12.8
3. T a = v;
The form of this statement is called copy-initialization by the C++ standard. This name is very confusing, leading me to
At one time, it was thought that this type of semantics (later explained what is semantic) must call copy ctor
Refer to the C++ standard 8.5/14, there is such a sentence
If the destination type is a (possibly cv-qualified) class type:
--If the initialization is direct-initialization, or if it is
copy-initialization where the cv-unqualified version of the
source type is the same class as, or a derived class of, the
class of the destination, constructors are considered. The
applicable constructors are enumerated (13.3.1.3), and the
best one is chosen through overload resolution (13.3). The
constructor so selected is called to initialize the object,
with the initializer expression(s) as its argument(s). If no
constructor applies, or the overload resolution is ambiguous,
the initialization is ill-formed.
To put it simply, the above meaning is that if T is UDT, in the "copy-initialization and cv- of v
unqualified type is also T (or T-derived class)", the implementation is the same
Same as direct-initialization!
*Note 2, actually copy-initialization in this case is different from direct-initialization,
* The C++ standard states that only T a(v); is to explicitly call ctor, otherwise it is implicitly called, which requires
* The selected ctor cannot be declared as explicit, otherwise the compilation error
*---C++ Standard 12.3.1
* An explicit constructor constructs objects just like non-explicit
* constructors, but does so only where the direct-initialization
* syntax (8.5) or where casts (5.2.9, 5.4) are explicitly used.
The C++ standard also stipulates that if not
--Otherwise (i.e., for the remaining copy-initialization cases),
user-defined conversion sequences that can convert from the source
type to the destination type or (when a conversion function is used)
to a derived class thereof are enumerated as described in 13.3.1.4,
and the best one is chosen through overload resolution (13.3). If the
conversion cannot be done or is ambiguous, the initialization is ill-formed.
The function selected is called with the initializer expression as its
argument; if the function is a constructor, the call initializes a
temporary of the destination type. The result of the call (which is
the temporary for the constructor case) is then used to direct-initialize,
according to the rules above, the object that is the destination of
the copy-initialization. In certain cases, an implementation is permitted
to eliminate the copying inherent in this direct-initialization by
constructing the intermediate result directly into the object being
initialized; see 12.2, 12.8.
To put it simply, the statement T a = v; when the type of v is different from T (and not a derived class of T), semantically
First construct a temporary object T with v (or call a custom conversion to implicitly cast to type T or a derived type of T),
Then explicitly call copy ctor to construct a
The reason why it emphasizes semantics is because the standard says that this call can be optimized away, but the semantics exist
The emphasis on explicit means that the copy ctor can be explicit
In addition, it should be emphasized that although the standard here says that after constructing the temporary object T(v) (or implicitly converting v
For type T, static_cast<T>(v)) and then use direct-initialize to construct a directly, the entire statement is executed
The form is T a(T(v)); (static_cast<T>(v) can also be written as T(v) in C syntax), but it is called with T(v)
ctor can only be a copy ctor of T, because the compiler has already performed a custom implicit conversion T(v), cannot
The second time, so you must call the ctor that directly matches the T(v) parameter, which is the copy ctor
Another addition to the above sentence is that the cast of the derived class to the base class is not a custom implicit conversion, it is automatically sent
Raw, so there is another option is Ta (U(v)); and U is a derived class of T! The final call is still copy ctor
Examples are as follows
struct T
{
T(){}
T(int){}
operator int(){return 0;}
private:
explicit T(T&){}
};
int main()
{
T a = T(); // Compiled, it is equivalent to implicit call T a(T());
// The overloading rule uses T(int), which is T a(static_cast<int>(T()));
// If T::T(int) is declared as explicit, compilation error
T b = 0; // compilation error, copy ctor can not access, although this call is optimized, but the semantics still exist
return 0;
}
Here is another example to supplement the explanation
struct T
{
T(int){}
explicit T(const T&){}
};
void foo(T)
{
}
int main()
{
foo(T(0)); // Compile error, according to *Note 2, it is required to implicitly copy the parameters to foo
// Equivalent to T t = T(0);
foo(0); // Compile and pass, construct a temporary object T(0) with 0, and then explicitly copy the parameters to foo
// Equivalent to T t = 0;
return 0;
}
Because the function's pass-by-value parameters and return-by-value are both copy-initialization, there will be differences in the results as above |
|