代码之家  ›  专栏  ›  技术社区  ›  BiagioF

在初始值设定项列表中复制构造

  •  7
  • BiagioF  · 技术社区  · 6 年前

    我在探索 丑陋的 世界 std::intializer_list .

    据我从标准中了解到:

    第十七节 :

    1. 从初始值设定项列表构造std::initializer_list类型的对象,就像实现 生成并具体化(7.4)n const e__类型_数组的prvalue,其中n是元素数。 在初始值设定项列表中。 该数组的每个元素都是用 初始化列表 ,并构造std::initializer_list对象以引用该数组。[注:A 构造函数 或选择转换函数 复印件 应在以下情况下可进入(第14条) 初始值设定项列表。尾注][…]

    所以,在这种情况下 E 是一个 我期待着 复制构造函数 被召唤。


    以下类不允许复制构造:

    struct NonCopyable {
      NonCopyable() = default;   
      NonCopyable(const NonCopyable&) = delete;
    };
    

    我将尝试实例化 std::initializer_list 和这个班。

    #include <vector>
    
    void foo() {
      std::vector<NonCopyable>{NonCopyable{}, NonCopyable{}};
    }
    

    g++-8.2 -std=c++14 我得到了我所期望的,编译器错误:

    error: use of deleted function 'NonCopyable::NonCopyable(const NonCopyable&)' .

    很完美!


    但是,行为会随着新标准的变化而变化。

    的确, g++-8.2 -std=c++17 编译。

    Compiler Explorer Test


    我以为这是因为 copy elision 一开始由新标准提供。

    但是,改变 标准库实现 (保持C++ 17)错误返回:

    clang-7 -std=c++17 -stdlib=libc++ 失败:

    'NonCopyable' has been explicitly marked deleted here NonCopyable(const NonCopyable&) = delete;

    Compiler Explorer Test


    那我错过了什么?

    1)C++ 17 要求 在中删除副本 复制构造 元素的 initializer_list ?

    2)为什么 libc++ 此处不编译实现?


    编辑 请注意,在示例中 g++ -std=c++17 (编译),如果我更改 默认构造函数 如“用户定义”所示:

    struct NonCopyable {
      NonCopyable();
      NonCopyable(const NonCopyable&) = delete;
    };
    

    程序不再编译(不是因为链接错误)。

    Compiler Explorer Example

    2 回复  |  直到 6 年前
        1
  •  5
  •   Barry    6 年前

    问题是这种类型:

    struct NonCopyable {
      NonCopyable() = default;   
      NonCopyable(const NonCopyable&) = delete;
    };
    

    微不足道的可复制 . 作为一个优化,因为 std::initializer_list 它只是由一个数组支持的,libstdc++所做的只是将整个内容memcping到 vector 作为优化。请注意,即使该类型有一个已删除的复制构造函数,它仍然是可复制的!

    这就是为什么当您使默认的构造函数用户(通过 ; 而不是 = default; )突然不再编译了。这使得该类型不再是普通的可复制类型,因此memcpy路径消失了。

    至于此行为是否正确,我不确定(我怀疑有一个要求,即此代码 不能 编译?我提交 89164 以防万一。你当然 希望 libstdc++要在琐碎的可复制情况下采用这条路径——但也许它需要排除这种情况?在任何情况下,您都可以通过另外删除复制分配操作符(您可能无论如何都想这样做)来完成相同的操作,这也将导致类型不可复制。

    这在C++ 14中没有编译,因为您不能构造 std::初始值设定项列表 -那里的复制初始化需要复制构造函数。但是在C++ 17中保证复制拷贝,构造了 std::初始值设定项列表 很好。但实际构建 矢量 完全独立于 std::初始值设定项列表 (实际上,这完全是一条红鲱鱼)。考虑:

    void foo(NonCopyable const* f, NonCopyable const* l) {
      std::vector<NonCopyable>(f, l);
    }
    

    在C++ 11中编译的很好…至少自合同通用条款第4.9款起。

        2
  •  1
  •   Nicol Bolas    6 年前

    C++ 17在初始化子列表元素的拷贝构造中是否需要复制删除?

    初始化的元素 initializer_list 从不保证使用“复制构造”。它只执行复制 初始化 . 复制初始化是否调用复制构造函数完全取决于初始化过程中发生了什么。

    如果你有一种可以从 int 你这样做 Type i = 5; ,即复制初始化。但它不会调用复制构造函数;而是调用 Type(int) 构造函数。

    是的,数组元素的构造 初始值设定项列表 引用容易被删除。包括C++ 17的保证规则。

    有人说,什么? 不是 易受这些规则影响的是 vector 它本身 . 矢量 必须从 初始值设定项列表 ,因此它们必须具有可访问的复制构造函数。编译器/库实现如何解决这个问题尚不清楚,但它肯定是不规范的行为。