代码之家  ›  专栏  ›  技术社区  ›  Edward Strange

mi和隐式复制构造函数错误(是:在什么条件下模板可以是复制构造函数?)

  •  4
  • Edward Strange  · 技术社区  · 15 年前

    我很确定这个问题的答案是,“从来没有,一个模板可以是复制构造函数。”

    不幸的是,我只花了3个小时来弄明白为什么我会收到关于递归的警告,跟踪到复制构造函数,观察调试器发疯,不让我查看递归代码,最后跟踪到基本构造函数中缺少的“&”。

    你看,我有一个复杂的基于策略的设计主机,它已经工作了一段时间了。我着手在一个中重写两个策略,并遇到了一个递归复制构造函数。把范围缩小到一个策略,该策略需要提供一个构造函数,该构造函数可以将一种类型的xxx概念作为其参数,但在本例中,我只是放弃了它。所以我写了

    struct my_policy
    {
      template < typename T >
      my_polity(T const) {} // missing '&'...oops
    };
    

    现在,我的策略是主机的一个基类(当然了),这个小错误导致了递归,主机的复制构造函数将自己沿着链传递给这个模板化的构造函数,而不是一个由编译器生成的隐式复制构造函数。当然,它会再次调用其复制构造函数来创建临时的。

    真正令人着迷的是我不能用简化的代码重新创建它。即使有一个模拟策略主机的例子,我也不能做到。以下代码不显示问题:

    #include <boost/utility/enable_if.hpp>
    #include <boost/mpl/bool.hpp>
    
    struct base
    {
      template < typename T >
      base(T const) {}
    };
    
    struct another_base 
    {
      int x;
    
      another_base(int y) : x(y) {}
    };
    
    template < typename T >
    struct is_derived : boost::mpl::false_ {};
    
    template < typename T1, typename T2 >
    struct derived : T1, T2
    {
      template < typename T >
      derived(T const& x, typename boost::disable_if< is_derived<T> >::type * = 0) : T1(0), T2(x) {}
    };
    
    template < typename T1, typename T2 >
    struct is_derived<derived<T1,T2>> : boost::mpl::true_ {};
    
    int main() 
    {
      derived<base, another_base> d(23);
      derived<base, another_base> x = d;
    }
    

    我正在使用Boost的参数库使主机的7个左右的参数可以通过“name”访问。也许这就是问题所在,我不知道。无论如何,我想知道是否有人知道特定的条件(如果有的话)会导致编译器合法地使用“base”的模板化构造函数作为复制构造函数,或者使用“derived”的隐式复制构造函数。

    编辑注释:

    我在上面的代码中通过给“另一个库”一个显式的复制构造函数重新创建了这个问题:

    struct another_base 
    {
      int x;
    
      another_base(another_base const& b) : x(b.x) {}
    
      another_base(int y) : x(y) {}
    };
    

    开始断定这是一个编译器错误,除非有人能告诉我为什么这是合法的。

    更多信息:

    struct derived;
    
    struct base
    {
      base() {}
    
    private:
      base(derived const&);
    };
    
    struct base2 
    {
      base2() {}
      //base2 (base2 const&) {}
    };
    
    struct derived : base, base2 {};
    
    int main()
    {
      derived d1; derived d2(d1);
    }
    

    再看看Schaub的答案,我把上面的代码编译了一下。在取消对base2的复制构造函数声明的注释之前,它编译得很好。然后它将以我假设的原始代码的预期方式爆炸(不能访问基中的私有构造函数)。因此模板甚至不是问题的一部分;您可以在没有模板的情况下重新创建问题。看起来这是一个心肌梗死的问题,而vs在纠正问题上总是有点慢。

    我改变了标签以反映这个发现。

    发布到MS的Bug存储库

    http://connect.microsoft.com/VisualStudio/feedback/details/587787/implicit-copy-constructor-calls-base-with-derived-type-under-specific-conditions

    我在示例代码中包含了一个变通方案。

    3 回复  |  直到 15 年前
        1
  •  3
  •   SCFrench    15 年前

    我很肯定你的问题答案是“永远不会”。

    ANSI C++标准的12.12.2节说

    类X的非模板构造函数 是一个 复制 如果它是第一个 参数的类型为x&、const x&, volatile x&或const volatile x&和 或者没有其他参数 否则所有其他参数 默认参数。

    第12.8.3节规定

    一个构造函数的声明 如果类x的第一个 参数的类型(可选 cv合格)x和 没有其他参数或其他所有参数 参数具有默认参数。一 成员函数模板从不 实例化以执行 类对象到其类的对象 类型。例:

    struct S { 
        template <typename T> S(T);
    };
    
    S f();
    
    void g() {
        S a( f() ); // does not instantiate member template
    }
    

    最后的例子

        2
  •  5
  •   Johannes Schaub - litb    15 年前

    在VisualC++中,派生复制构造函数参数的基础委托存在一个问题:

    struct Derived;
    
    struct Base {
      Base(){ }
    
    private:
      Base(Derived const&);
    };
    
    struct Derived : Base { };
    
    Derived d1;
    Derived d2(d1);
    

    此代码是有效的,但是VisualC++无法编译它,因为它们调用基类复制构造函数,使用 Derived 对象。但是,该标准要求编译器通过 Base const (或) Base 在某些情况下)到基类。

    这是难题的第一部分:模板是更好的匹配,因为复制构造函数需要一个派生到基的转换,但是模板直接接受派生类,然后需要另一个副本,等等。注意模板将 在这里充当复制构造函数(给定vc++bug),正如上面的声明 Base(Derived const&) 未声明复制构造函数。

    第二部分是您的另一个问题的答案:标准是含糊不清的,在C++ 03中不清楚实例化模板是否可以充当复制构造函数。在纸条上写着

    因为模板构造函数从来不是复制构造函数,所以存在这样的模板不会抑制复制构造函数的隐式声明。模板构造器与其他构造器(包括复制构造器)一起参与重载解决方案,如果模板构造器提供了比其他构造器更好的匹配,则可以使用模板构造器复制对象。

    但下面几段,它说

    永远不会实例化成员函数模板来执行类对象到其类类型对象的复制。

    由于此文本出现在上下文中(禁止按值参数复制构造函数),人们可能会认为这并不是禁止从模板中实例化按引用复制构造函数。但面对这种含糊不清的措辞,这种争论是没有意义的。

    C++ 0x FCD清除了它,并删除了奇怪的音符。现在很明显,无论模板是通过引用还是通过值参数生成,都不会实例化模板来执行复制。但是如上所述,如果您碰巧使用VC++并且它表现出这种行为,那么它与复制构造函数没有任何关系。

        3
  •  3
  •   YeenFei    15 年前

    可能要指出以下声明是 不是 复制构造函数。

    template < typename T >
      derived(T const& x, typename boost::disable_if< is_derived<T> >::type * = 0) : T1(0), T2(x) {}
    

    的复制构造函数 derived<base, another_base> 应该采取 const derived<base, another_base>& 作为输入,而不是任意输入 const derived<X,Y>&


    复制构造函数的第一个 参数A(可能是常量或 volatile)对其自身类的引用 类型。它可以有更多的论点,但是 其余部分必须具有默认值 与他们有关联。

    http://en.wikipedia.org/wiki/Copy_constructor