【问题标题】:Why is std::string s = "123" considered copy-initialisation when no copy is involved?为什么在不涉及复制时将 std::string s = "123" 视为复制初始化?
【发布时间】:2020-06-04 03:15:51
【问题描述】:

为什么是这种初始化 std::string 的方式: std::string s = "123"; 当没有实际发生任何复制时,是否认为是复制初始化?

在上面的例子中,编译器可以清楚地看到有一个std::string构造函数接受char const *,因此这里发生的是通过@的隐式转换构造一个std::string对象987654325@ 至std::string。这是一个如此明确的场景。它只是简单地调用std::string(const char *) 构造函数一次,简单明了。如此简单,对于复制省略和移动等优化,甚至没有什么可谈的。

现在,问题是,我从来没有对通过隐式转换(即类 a = 表达式)进行对象初始化有任何困惑,直到我开始遇到文献声明 = 的初始化是“复制初始化”。甚至主要人物 Bjarne Stroustrup 本人也将这种形式的初始化称为“复制初始化”。

在这一点上,我觉得我可能误会了什么。

那么,如果允许隐式转换显然不是这种情况,为什么 = 的初始化被认为是复制初始化?

【问题讨论】:

    标签: c++


    【解决方案1】:

    术语copy-initialization 仅用于以下形式的初始化语法:

    T object = other;
    

    此初始化的效果之一是:

    如果 T 是类类型,并且 other 的类型的 cv 非限定版本不是 T 或派生自 T,或者如果 T 是非类类型,但 other 的类型是类类型,用户-检查定义的转换序列,这些转换序列可以从 other 的类型转换为 T(如果 T 是类类型且转换函数可用,则转换为从 T 派生的类型),并通过重载决议选择最佳的。

    所以对于表达式:

    std::string s = "123"; 
    

    采用const char * 的隐式constructor 用于构造std::string

    因此,即使其中包含术语 copy,复制初始化并不意味着涉及实际的 copy,它之所以这样称呼,是因为语法使它出现就像正在复制一样。

    【讨论】:

    • 谢谢 - 我想你在这里找到了问题的核心:" ... 可以从 other 类型转换为 T ... 的用户定义的转换序列被检查并且选择了最好的...” 因此,这清楚地表明隐式转换确实是“复制初始化”定义的一部分。我需要以律师的眼光阅读规格! :)
    • 只有当你想知道确切的规则时:) 大多数 c++ 程序员不需要知道大部分细节来编写好的代码。另请注意,我引用的是 cppreference,而不是标准,它是官方文档。
    【解决方案2】:

    之所以称为复制初始化,是因为在 C++11 之前,根据语言规则,这实际上是必须要做的。当你有

    T t = u;
    

    如果uT,则调用复制构造函数。所以在这种情况下称它为复制初始化是有意义的

    如果u 不是T,那么 [dcl.init]/15 bullet 7 开始发挥作用 (from the C++03 draft) 并且具有

    否则(即,对于剩余的复制初始化情况),可以从源类型转换到目标类型或(当使用转换函数时)到其派生类的用户定义转换序列被枚举为描述在 13.3.1.4 中,通过重载决议 (13.3) 选择最好的一个。如果转换无法完成或不明确,则初始化格式错误。以初始化表达式作为参数调用所选函数; 如果函数是构造函数,则调用会初始化目标类型的 cv 非限定版本的临时版本。临时值是一个右值。然后根据上述规则,调用的结果(对于构造函数的情况是临时的)用于直接初始化作为复制初始化目标的对象。 在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除这种直接初始化中固有的复制;见 12.2、12.8。

    强调我的

    声明创建了一个临时对象,并且该临时对象用于初始化它们。是的,它说在某些情况下可以避免这种情况,但那些部分只允许优化,不是强制发生的。

    所以,我们再次进行复制,所以复制初始化是有意义的。

    使用 C++17 不再是这种情况,您保证不会存在任何副本,但此时仍使用名称。

    【讨论】:

    • 抛开优化,无论 C++ 版本如何,如果 u 不是 T,如果存在将 u 隐式转换为 T 的构造函数,则不会发生复制构造。上述规范中的相关部分是: ... 可以从源类型转换为目标类型的用户定义的转换序列或...被枚举...并且通过重载决议选择最佳的...,如@cigien 的回答指出了
    • @moog cigien 使用最新的标准,让您认为它是真实的。在过去,这不是真的,或者至少不能保证不会发生复制。这就是 C++11 和 C++17 如此出色的原因。 C++11 引入了移动语义,因此如果进行了复制,则可能会发生移动,而 C++17 最终告诉你不做什么,没有理智的编译器不会优化它,所以让我们保证行为。
    猜你喜欢
    • 2017-12-21
    • 2013-09-14
    • 2010-09-26
    • 2021-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多