本文介绍了复制初始化和直接初始化有区别吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有这个功能:

void my_test()
{
    A a1 = A_factory_func();
    A a2(A_factory_func());

    double b1 = 0.5;
    double b2(0.5);

    A c1;
    A c2 = A();
    A c3(A());
}

在每个分组中,这些陈述是否相同?或者在一些初始化中是否有额外的(可能可以优化的)副本?

In each grouping, are these statements identical? Or is there an extra (possibly optimizable) copy in some of the initializations?

我见过人们说这两件事.请引用文本作为证据.还请添加其他案例.

I have seen people say both things. Please cite text as proof. Also add other cases please.

推荐答案

C++17 更新

在 C++17 中,A_factory_func() 的含义从创建临时对象 (C++a1)、初始化最终被丢弃时创建的人工对象,或者引用绑定需要一个对象(如,在A_factory_func();中.在最后一种情况下,一个对象是人为创建的,称为临时物化",因为A_factory_func()没有变量或引用否则将需要一个对象存在).

C++17 Update

In C++17, the meaning of A_factory_func() changed from creating a temporary object (C++<=14) to just specifying the initialization of whatever object this expression is initialized to (loosely speaking) in C++17. These objects (called "result objects") are the variables created by a declaration (like a1), artificial objects created when the initialization ends up being discarded, or if an object is needed for reference binding (like, in A_factory_func();. In the last case, an object is artificially created, called "temporary materialization", because A_factory_func() doesn't have a variable or reference that otherwise would require an object to exist).

在我们的例子中,在 a1a2 的情况下,特殊规则说在这样的声明中,与相同类型的纯右值初始值设定项的结果对象a1 是变量a1,因此A_factory_func() 直接初始化对象a1.任何中间函数风格的转换都不会产生任何效果,因为 A_factory_func(another-prvalue) 只是传递"了外层纯右值的结果对象,也是内层纯右值的结果对象.

As examples in our case, in the case of a1 and a2 special rules say that in such declarations, the result object of a prvalue initializer of the same type as a1 is variable a1, and therefore A_factory_func() directly initializes the object a1. Any intermediary functional-style cast would not have any effect, because A_factory_func(another-prvalue) just "passes through" the result object of the outer prvalue to be also the result object of the inner prvalue.

A a1 = A_factory_func();
A a2(A_factory_func());

取决于 A_factory_func() 返回的类型.我假设它返回一个 A - 然后它做同样的事情 - 除了当复制构造函数是显式的时,第一个会失败.阅读 8.6/14

Depends on what type A_factory_func() returns. I assume it returns an A - then it's doing the same - except that when the copy constructor is explicit, then the first one will fail. Read 8.6/14

double b1 = 0.5;
double b2(0.5);

这样做是一样的,因为它是一个内置类型(这意味着这里不是类类型).阅读 8.6/14.

This is doing the same because it's a built-in type (this means not a class type here). Read 8.6/14.

A c1;
A c2 = A();
A c3(A());

这不是在做同样的事情.如果 A 是非 POD,则第一个默认初始化,并且不对 POD 进行任何初始化(阅读 8.6/9).第二个副本初始化:值初始化一个临时值,然后将该值复制到 c2(阅读 5.2.3/28.6/14).这当然需要一个非显式复制构造函数(阅读 8.6/1412.3.1/313.3.1.3/1).第三个函数为 c3 函数创建了一个函数声明,该函数返回一个 A 并接受一个函数指针,该函数指针返回一个 A(读取 Aa href="http://eel.is/c++draft/dcl.ambig.res" rel="noreferrer">8.2).

This is not doing the same. The first default-initializes if A is a non-POD, and doesn't do any initialization for a POD (Read 8.6/9). The second copy initializes: Value-initializes a temporary and then copies that value into c2 (Read 5.2.3/2 and 8.6/14). This of course will require a non-explicit copy constructor (Read 8.6/14 and 12.3.1/3 and 13.3.1.3/1 ). The third creates a function declaration for a function c3 that returns an A and that takes a function pointer to a function returning a A (Read 8.2).

深入研究初始化直接和复制初始化

虽然它们看起来相同并且应该做相同的事情,但这两种形式在某些情况下却截然不同.初始化的两种形式是直接初始化和复制初始化:

While they look identical and are supposed to do the same, these two forms are remarkably different in certain cases. The two forms of initialization are direct and copy initialization:

T t(x);
T t = x;

我们可以将行为归因于每个人:

There is behavior we can attribute to each of them:

  • 直接初始化的行为就像对重载函数的函数调用:在这种情况下,函数是 T 的构造函数(包括 explicit 的构造函数)和参数是 x.重载解析将找到最匹配的构造函数,并在需要时进行任何所需的隐式转换.
  • 复制初始化构造了一个隐式转换序列:它尝试将x 转换为T 类型的对象.(然后它可能会将该对象复制到要初始化的对象中,因此也需要一个复制构造函数 - 但这在下面并不重要)
  • Direct initialization behaves like a function call to an overloaded function: The functions, in this case, are the constructors of T (including explicit ones), and the argument is x. Overload resolution will find the best matching constructor, and when needed will do any implicit conversion required.
  • Copy initialization constructs an implicit conversion sequence: It tries to convert x to an object of type T. (It then may copy over that object into the to-initialized object, so a copy constructor is needed too - but this is not important below)

如您所见,复制初始化在某种程度上是关于可能的隐式转换的直接初始化的一部分:虽然直接初始化具有所有可调用的构造函数,此外em> 可以做任何它需要的隐式转换来匹配参数类型,复制初始化可以只设置一个隐式转换序列.

As you see, copy initialization is in some way a part of direct initialization with regard to possible implicit conversions: While direct initialization has all constructors available to call, and in addition can do any implicit conversion it needs to match up argument types, copy initialization can just set up one implicit conversion sequence.

我努力尝试,得到以下代码来为每个表单输出不同的文本,通过 explicit 构造函数不使用obvious".

I tried hard and got the following code to output different text for each of those forms, without using the "obvious" through explicit constructors.

#include <iostream>
struct B;
struct A {
  operator B();
};

struct B {
  B() { }
  B(A const&) { std::cout << "<direct> "; }
};

A::operator B() { std::cout << "<copy> "; return B(); }

int main() {
  A a;
  B b1(a);  // 1)
  B b2 = a; // 2)
}
// output: <direct> <copy>

它是如何工作的,为什么会输出那个结果?

How does it work, and why does it output that result?

  1. 直接初始化

它首先对转换一无所知.它只会尝试调用构造函数.在这种情况下,以下构造函数可用并且是完全匹配:

It first doesn't know anything about conversion. It will just try to call a constructor. In this case, the following constructor is available and is an exact match:

B(A const&)

没有转换,更不用说用户定义的转换,需要调用该构造函数(请注意,这里也没有发生 const 限定转换).所以直接初始化会调用它.

There is no conversion, much less a user defined conversion, needed to call that constructor (note that no const qualification conversion happens here either). And so direct initialization will call it.

复制初始化

如上所述,当a 没有类型B 或派生自B 时,复制初始化将构造一个转换序列(这里显然就是这种情况).所以它会寻找方法进行转换,并会找到以下候选

As said above, copy initialization will construct a conversion sequence when a has not type B or derived from it (which is clearly the case here). So it will look for ways to do the conversion, and will find the following candidates

B(A const&)
operator B(A&);

注意我是如何重写转换函数的:参数类型反映了this指针的类型,它在非常量成员函数中是非常量的.现在,我们用 x 作为参数来调用这些候选对象.获胜者是转换函数:因为如果我们有两个候选函数都接受对同一类型的引用,那么 less const 版本将获胜(顺便说一下,这也是一种更喜欢非-const 成员函数调用非常量对象).

Notice how I rewrote the conversion function: The parameter type reflects the type of the this pointer, which in a non-const member function is to non-const. Now, we call these candidates with x as argument. The winner is the conversion function: Because if we have two candidate functions both accepting a reference to the same type, then the less const version wins (this is, by the way, also the mechanism that prefers non-const member function calls for non-const objects).

注意,如果我们把转换函数改成const成员函数,那么转换是不明确的(因为两者都有一个A const&的参数类型):Comeau编译器正确拒绝了,但 GCC 在非迂腐模式下接受它.不过,切换到 -pedantic 也会使其输出正确的歧义警告.

Note that if we change the conversion function to be a const member function, then the conversion is ambiguous (because both have a parameter type of A const& then): The Comeau compiler rejects it properly, but GCC accepts it in non-pedantic mode. Switching to -pedantic makes it output the proper ambiguity warning too, though.

我希望这有助于更清楚地说明这两种形式的不同之处!

I hope this helps somewhat to make it clearer how these two forms differ!

这篇关于复制初始化和直接初始化有区别吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

05-27 10:49
查看更多