【问题标题】:Multiple copy constructors specified指定了多个复制构造函数
【发布时间】:2011-12-14 14:35:28
【问题描述】:

使用 Visual C++ 2010,我有一个这样的类:

class MyClass{
public:
    MyClass(){}
    MyClass(MyClass &){/*...*/}   //A
    MyClass(const MyClass &){/*...*/}   //B
    template<typename T> MyClass(T &&t){ static_assert(
        !std::is_same<typename 
        std::remove_cv<typename  std::remove_reference<T>::type>::type, 
        MyClass>::value,
        "Wrapping over wrapping is not allowed!"); } //C
};

int main(int, char**){
    MyClass a;
    const MyClass b(a); //assert fail if line A removed
    auto c=b; //assert fail if line B removed
}
//If both A and B exists
//Warning C4521: multiple copy constructors specified
//Furthermore, if both A and B removed
//No error or warnings; VC2010 accepts peacefully.
//In debug mode you will find the compiler generated trivial copy constructor

根据 C++ 标准,A 行和 B 行都被认为是复制构造函数,而 C 是转换构造函数。我收到一条警告说我声明了多个复制构造函数,这并不奇怪。但是,如果我删除它们中的任何一个,static_assert 将失败并且代码将无法编译,这意味着模板构造函数获得了控制权。

我确信这种行为遵循函数重载的规则。然而,这是两个规则的冲突吗?如果 A 和 B 是复制构造函数并且声明了其中一个,那么任何复制对象的尝试都不应该放到模板中,对吗?

更新: 根据 N3242,12.8.7,

“从不实例化成员函数模板来执行将类对象复制到其类类型的对象。”

正确的实现应该是:

  • 当 A 或 B 或两者都被移除时,不会发生断言失败。
  • 如果删除行 B,c 的构造应该会失败,因为 b 是 const。
  • 如果删除了这两行,编译器应该为该类生成一个复制构造函数。
  • 如果两条线都存在,则由实现来警告用户。

有什么意见吗?

【问题讨论】:

  • 你自己带来了这个,包括static_assert。我想不出任何理由不允许从类型 T&amp; 转换为类型 const T&amp; - 你可以吗?
  • 这只是我的类的一个虚拟实现。模板构造函数的逻辑应该与复制构造函数不同。
  • 为了实现模板转换构造函数,通常应该使用一个包含的存储来存储t的值。如果 t 是 MyClass 的实例,它将引入 wrapping over wrapping,这是我要防止的。
  • 我明白你为什么现在问。原始代码具有误导性。我已经更新了代码。

标签: templates visual-c++ constructor c++11 gcc4


【解决方案1】:

首先,template&lt;T&gt; 应该是template &lt;typename T&gt;

我在 64 位 Ubuntu Linux 上使用 gcc 4.4.3,代码的行为与您在帖子中演示的不同。

  • 如果没有任何变化,则可以编译代码而不会发出任何警告。构造函数 A 和 B 依次被调用。
  • 如果我注释 A 行,它就不能像你说的那样编译:在 const MyClass b(a); 行失败。原因是对象a不是常量,构造函数B无法匹配,编译器必须实例化模板构造函数。当然,const MyClassMyClass 是不同的类型。
  • 但是,如果我只注释 B 行,则可以成功编译代码并调用模板复制构造函数。对象 b 是一个常量对象,所以构造 A 无法匹配,编译器实例化模板构造函数。但是,问题仍然存在:static_assert 是否会失败?差异可能是由于平台/编译器的差异。 GCC 似乎实现了is_same&lt;MyClass&amp;&amp;, MyClass&gt;::value 是真的。您可以使用 typeid 打印出这两种类型。

【讨论】:

  • 显然有不同的实现。如果您在 GCC 中同时删除 A 和 B,您将得到一个失败的断言,但在 VC++ 中不会发生这种情况。我参考了最新的草案(N3242),似乎说复制构造函数是非模板(12.8.2)。所以至少在这种情况下,GCC 似乎反对它并允许模板版本是复制构造函数。
  • 我应该说标准说“永远不会实例化成员函数模板来执行将类对象复制到其类类型的对象”(12.8.7)。据此,我认为 GCC 和 VC2010 都未能遵循。如果 A 和 B 都被删除,GCC 会实例化模板版本;并且 VC2010 在其中一个被删除但不是全部时实例化了模板版本。
  • 对于类比较断言,正确的表达式应该是!std::is_same&lt;typename std::remove_cv&lt;typename std::remove_reference&lt;T&gt;::type&gt;::type, MyClass&gt;::value
  • 我将把它标记为正确答案,因为它包含另一个 GCC 案例研究。
  • 是的,在使用 std::is_same 时,请始终记住删除引用和常量修饰符。
【解决方案2】:

如果 A 和 B 是复制构造函数并且声明了其中一个,则任何复制对象的尝试都不应该放到模板中,对吗?

非复制构造函数的构造函数仍可用于复制对象。在您的情况下,从构造函数模板实例化的构造函数用于复制对象。这很好。

【讨论】:

  • 这涉及到另一个问题。如果 A 行和 B 行都被删除,Visual Studio 2010 会生成一个简单的复制构造函数,并且对象 b 和 c 的构造都不会放到模板中,即编译器会愉快地接受代码,这也不是我想要的。跨度>
猜你喜欢
  • 1970-01-01
  • 2017-11-29
  • 2013-04-04
  • 2010-10-21
  • 2016-04-13
  • 2011-02-09
  • 1970-01-01
  • 2013-10-13
  • 1970-01-01
相关资源
最近更新 更多