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

C++ 14与C++ 17的默认构造函数调用差异

  •  2
  • Smeeheey  · 技术社区  · 7 年前

    考虑以下代码(问题如下):

    #include <iostream>
    
    struct Type0
    {
        Type0(char* c)
        {}
    };
    
    struct Type1
    {
        Type1(int* i=nullptr) : i_(i)
        {}
    
        Type1(const Type1& other) = default;
    
        int* i_;
    };
    
    template <typename ...>
    struct Composable;
    
    template <typename T0, typename ... T>
    struct Composable<T0, T...> : T0, Composable<T...>
    {
        Composable()
        {
            std::cout << "Default Invoked: " << sizeof...(T) << std::endl;
        }
    
        Composable(const Composable& other) = default;
    
        template<typename Arg, typename ... Args>
        Composable(Arg&& arg, Args&& ... args) :
            T0(std::forward<Arg>(arg)), Composable<T...>(std::forward<Args>(args)...) 
        {
            std::cout << "Non-default invoked: " << sizeof...(T) << std::endl;
        }
    };
    
    template <>
    struct Composable<>{};
    
    int main()
    {
        int i=1;
        char c='c';
    
        auto comp = Composable<Type0, Type1>(&c, &i);
    
        std::cout << comp.i_ << std::endl;
    }
    

    你可以找到实时代码 here . 此代码有一个有趣的属性:取决于是否使用 --std=C++17 或 --std=C++14 选项,行为会发生变化(您可以在我指向实时代码的链接中尝试此操作:编辑g++调用 --std 参数位于左下角)。

    用 --std=c++14 ,得到以下输出:

    Non-default invoked: 0
    Non-default invoked: 1
    Default Invoked: 0
    Non-default invoked: 1
    0x0
    

    用 ——STD=C++ 17 ,您可以改为:

    Non-default invoked: 0
    Non-default invoked: 1
    0x7ffcdf02766c
    

    对我来说,这种差异令人困惑。显然,C++ 17版本正在做正确的事情,而C++ 14是错误的。C++ 14版本正在调用两个默认构造函数。 Composable (从中) Type1 (这里是 0x0 最后一行输出来自 1型 将此作为其默认值 i 构造函数参数)。但是,我看不到任何应该调用默认构造函数的地方。

    此外,如果我注释掉 可合成的 总之,C++ 17版本与以前完全一样,而C++ 14版本现在无法编译,抱怨缺少一个默认构造函数。如果有任何希望通过不同的优化行为以某种方式解释这种差异,那么这个事实肯定会扼杀它(希望很小,因为观察到的差异在所有优化级别上都存在,包括0)。

    有人能解释这种区别吗?C++ 14的行为是一个bug,还是一些我不理解的行为?如果C++ 14的行为在C++ 14的规则中是正确的,那么有人能解释一下默认构造函数调用来自何处吗?

    1 回复  |  直到 7 年前
        1
  •  7
  •   Barry    7 年前

    保证删除副本。

    这条线:

    auto comp = Composable<Type0, Type1>(&c, &i);
    

    在C++ 17中,这意味着完全相同的事情:

    Composable<Type0, Type1> comp(&c, &i);
    

    如果你改变了这个版本,你会看到C++ 14和C++ 17的相同行为。但是,在C++ 14中,这仍然是一个移动构造(或者,在技术上是正确的,正如您将在一分钟内看到的,复制初始化)。但在 Composable ,您没有隐式生成的移动构造函数,因为您有一个用户声明的复制构造函数。因此,对于移动构造,在C++ 14版本中调用了“非默认调用”构造函数模板(它是比复制构造函数更好的匹配):

    template<typename Arg, typename ... Args>
    Composable(Arg&& arg, Args&& ... args) :
        T0(std::forward<Arg>(arg)), Composable<T...>(std::forward<Args>(args)...) 
    

    在这里, Arg 是 Composable<Type0, Type1> 和 Args 是一个空包。我们授权给 T0 ( Type0 )的构造函数,转发整个 可合成的 (因为它继承自 0型 因此,我们在那里得到隐式生成的move构造函数)和 Composable<Type1> 的默认构造函数(因为 args 是空的)。

    此构造函数模板不是 真的? 一个正确的移动构造函数-它不会最终初始化 Type1 全体成员。而不是从右侧移动 Type1::i_ 你在召唤 Type1::Type1() ,默认的构造函数,这就是为什么最终 0 .

    如果添加适当的移动构造函数:

    Composable(Composable&& other) = default;
    

    然后,您将再次看到C++ 14和C++ 17之间的相同行为。