MI和隐式复制构造函数bug(是:在什么条件下模板可以作为复制构造函数?)

时间:2010-08-20 00:02:18

标签: c++ multiple-inheritance

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

不幸的是,我花了3个小时搞清楚为什么我会收到关于递归的警告,跟踪它到复制构造函数,看着调试器变得疯狂而不让我看看递归代码,最后追踪它到了失踪'&'在基础构造函数中。

你知道,我有这个复杂的基于策略的设计主机,现在已经工作了一段时间。我在一个中重写了两个策略并遇到了一个递归复制构造函数。将其缩小为一个策略,该策略需要提供一个构造函数,该构造函数可以采用某种类型的XXX概念作为其参数,但在这种情况下,我只是丢弃它。所以我写了

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

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

真正令人着迷的是我无法在简化代码中重新创建它。即使有一种模拟政策主持人的例子,我也无法实现。以下代码不会出现此问题:

#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的参数库使“name”可以访问主机的7个左右参数。也许这就是问题,我不知道。无论如何,我想知道是否有人在那里知道具体条件(如果有的话)可能导致编译器合法地使用模板化构造函数将“base”作为复制构造函数或者从“derived”的隐式复制构造函数中使用。 / p>

编辑备注:

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

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的复制构造函数声明。然后它会以我假设的原始代码(无法访问基础中的私有构造函数)的方式爆炸。所以模板甚至不是问题的一部分;没有它们你可以重新创建问题。看起来这是一个MI问题,VS在纠正方面总是有点慢。

我已更改标签以反映此发现。

发布到MS的错误存储库

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

我在示例代码中包含了一个解决方法。

3 个答案:

答案 0 :(得分:5)

使用Visual C ++,导致复制构造函数参数的基本委派存在问题:

struct Derived;

struct Base {
  Base(){ }

private:
  Base(Derived const&);
};

struct Derived : Base { };

Derived d1;
Derived d2(d1);

此代码有效,但Visual C ++无法编译它,因为它们使用Derived对象调用基类复制构造函数。但是,标准要求编译器将Base const(或某些情况下为Base)传递给基类。

这是你的谜题的第一部分:模板是一个更好的匹配,因为复制构造函数需要派生到基本转换,但你的模板直接接受派生类,然后需要另一个副本,依此类推等等。请注意,模板将在此处充当复制构造函数(给定VC ++错误),就像上面Base(Derived const&)的声明未声明复制构造函数一样。

第二部分是你的另一个问题的答案:标准是模棱两可的,在C ++ 03中不清楚实例化的模板是否可以作为复制构造函数。在一张纸条中,它说

  

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

但是下面有几段,它说

  

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

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

C ++ 0x FCD清除了它,删除了奇怪的音符。现在很清楚,模板永远不会被实例化以执行副本,无论它是否会产生副引用或按值参数。但是如上所述,如果您碰巧使用VC ++,并且它表现出这种行为,那么它与复制构造函数无关。

答案 1 :(得分:4)

我很确定你问题的答案是“从不”。

ANSI C ++标准的第12.8.2节说

  

X类的非模板构造函数   如果是第一个,则是副本构造函数   参数是X&amp;类型,const X&amp;,   挥发性X&amp;或const volatile X&amp;,和   要么没有其他参数   或者所有其他参数都有   默认参数。

第12.8.3节说

  

一个构造函数的声明   如果是第一类,则X级是不正确的   参数是类型(可选   cv-qualified)X和其中之一   没有其他参数或其他所有参数   参数有默认参数。一个   成员函数模板永远不会   实例化以执行a的副本   class对象到其类的对象   类型。 [实施例:

struct S { 
    template <typename T> S(T);
};

S f();

void g() {
    S a( f() ); // does not instantiate member template
}
  

- 结束示例]

答案 2 :(得分:3)

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

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(可能是const或   volatile)引用自己的类   类型。它可以有更多的参数,但是   其余的必须有默认值   与他们联系。

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