不可复制的对象和值初始化:g++ vs msvc
Posted
技术标签:
【中文标题】不可复制的对象和值初始化:g++ vs msvc【英文标题】:non-copyable objects and value initialization: g++ vs msvc 【发布时间】:2010-04-19 23:11:23 【问题描述】:我看到 g++ 和 msvc 之间围绕值初始化不可复制对象的一些不同行为。考虑一个不可复制的类:
class noncopyable_base
public:
noncopyable_base()
private:
noncopyable_base(const noncopyable_base &);
noncopyable_base &operator=(const noncopyable_base &);
;
class noncopyable : private noncopyable_base
public:
noncopyable() : x_(0)
noncopyable(int x) : x_(x)
private:
int x_;
;
以及一个使用值初始化的模板,这样即使类型是 POD,值也会得到一个已知值:
template <class T>
void doit()
T t = T();
...
并尝试将它们一起使用:
doit<noncopyable>();
从 VC++ 9.0 开始,这在 msvc 上运行良好,但在我测试过的每个 g++ 版本(包括 4.5.0 版)上都失败了,因为复制构造函数是私有的。
两个问题:
-
哪种行为符合标准?
关于如何在 gcc 中解决此问题的任何建议(需要明确的是,将其更改为
T t;
是不可接受的解决方案,因为这会破坏 POD 类型)。
附:我在 boost::noncopyable 上看到了同样的问题。
【问题讨论】:
我很确定 MSVC 不合规,让我看看报价。 (我认为编译器可以省略副本只要复制构造函数可用。) Comeau (comeaucomputing.com/tryitout) 支持 GMan。即使是最近的 VC 版本,我也看到了这方面的一些错误。 当您说“变通”时,是否可以有两个函数模板,一个用于 POD 类型,一个用于非 POD 类型(并使用 SFINAE 在它们之间进行选择)? 几乎“没有办法”拥有你想要的东西。 C++ 非常强大。在这种情况下,解决方法甚至不是那么邪恶(虽然肯定比原来的更丑)。 这就是为什么我变得如此犹豫要不要说 C++ 不可能的事情......我说了一半的时候,有人过来证明我错了。 【参考方案1】:您在 MSVC 中看到的行为是一个扩展,尽管在下一页(强调我的)http://msdn.microsoft.com/en-us/library/0yw5843c.aspx 上以迂回的方式记录了它:
等号初始化语法与函数样式语法不同,尽管生成的代码在大多数情况下是相同的。不同之处在于,当使用等号语法时,编译器必须表现得好像发生了以下事件序列:
创建一个与正在初始化的对象类型相同的临时对象。 将临时对象复制到对象。在编译器执行这些步骤之前,必须可以访问构造函数。尽管在大多数情况下编译器可以消除临时创建和复制步骤,但不可访问的复制构造函数会导致等号初始化失败(在 /Za、/Ze(禁用语言扩展)下)。
See Ben Voigt's answer 的解决方法是boost::value_initialized
的简化版本,正如 litb 在对 Ben 的回答的评论中指出的那样。 boost::value_initalized
的文档对问题、解决方法以及各种编译器问题的一些陷阱进行了很好的讨论。
【讨论】:
谢谢。虽然看起来 MSVC 有更大的问题。当私有复制构造函数和 operator= 被放入基类时(boost::noncopyable 的实现方式),MSVC 总是允许等号初始化。有人知道如何向 MS 编译器团队报告错误吗? 并在此处提及错误编号(或更好的是链接),以便我们对其进行投票。 @BenVoigt - 错误 # 是 552586 (connect.microsoft.com/VisualStudio/feedback/details/552586/…) 当复制构造函数不可访问时,该错误是否实际上允许复制构造,或者仅提供一种替代(非标准)语法来避免“最令人烦恼的解析”?我的意思是,它只是省略了复制构造函数并且无法执行可访问性检查,还是它还从本身不是由构造函数调用临时形成的右值复制构造? @BenVoigt - 错误报告的更新 - 刚刚从 MS 那里得到这个“我可以确认这确实是一个编译器错误。但是,鉴于我们的日程安排和资源限制,我们认为这个错误并不严重足以保证此时修复。但是,我们将考虑在未来的版本中修复此问题。感谢您抽出时间提醒我们注意这个问题!Andy Rich,Visual C++ QA"【参考方案2】:我认为不需要模板元编程。试试
template <class T>
void doit()
struct initer T t; initer() : t() inited;
T& t = inited.t;
...
【讨论】:
虽然我自己“重新发现”了这个解决方法,但事实证明 C++0x 草案非常清楚地指出了它,在 § 8.5 P 10 中提到了可以使用值初始化和ctor-initializer 列表就是其中之一。 确实非常好 - 比我想到的 TMP/SFINAE 路径要简单和优雅得多。我希望我能做的不止是 +1。 @Michael,@James,@Ben,这是boost::value_init
:boost.org/doc/libs/1_42_0/libs/utility/value_init.htm。值得注意的是,C++0x 允许 T t;
【参考方案3】:
有§12.8/14:
如果隐式使用对象的复制构造函数或复制赋值运算符并且特殊成员函数不可访问,则程序是非良构的。
然后是 §12.8/15:
当满足某些条件时,允许实现省略类对象的复制构造,即使对象的复制构造函数和/或析构函数有副作用。
所以,问题是,如果实现省略了对复制构造函数的调用(显然允许这样做),那么复制构造函数真的使用吗?
而且,根据 §3.2/2,答案是肯定的:
即使调用实际上被实现省略了,也会使用复制构造函数。
【讨论】:
啊,终于有人有章有节了。 +1!【参考方案4】:您是否看到使用 /Wall 和 MSVC 进行编译时会发生什么?它说明了您的班级以下内容:
nocopy.cc(21) : warning C4625: 'noncopyable' : copy constructor could not be
generated because a base class copy constructor is inaccessible
nocopy.cc(21) : warning C4626: 'noncopyable' : assignment operator could not be
generated because a base class assignment operator is inaccessible
GCC 补救措施:
为noncopyable
创建一个复制构造函数(理想情况下是一个赋值运算符!),它可以从noncopyable_base
复制信息,即调用没有参数的noncopyable_base
的构造函数(因为这是唯一可访问的构造函数) noncopyable
),然后从 noncopyable_base
复制任何数据。然而,鉴于noncopyable_base
的定义,似乎没有要复制的数据,因此将noncopyable_base()
简单地添加到新noncopyable(const noncopyable &)
函数的初始化列表中应该可以工作。
不过,请注意 MSVC 对您的程序的评价。另请注意,如果您使用T t()
而不是T t = T()
,则another warning (C4930) 是由MSVC 生成的,尽管GCC 很乐意接受它而不会发出任何警告。
【讨论】:
T t()
不等价,因为解析最麻烦;你必须做T t((T()))
,它应该得到与T t = T()
相同的结果。
@Dustin - 你错过了noncopyable_base
的重点。它旨在使类不可复制。见boost.org/doc/libs/1_42_0/libs/utility/…
幸运的是,ctor-initializer 列表不受最麻烦的解析,因为构造函数调用是唯一允许的解释。
@Ben:我很乐意相信 - 如果 你可以解释为什么 T f();
不能解释为函数 f
的声明,不接受参数并返回T
。
@sbi:首先你给我看一个包含T f();
的ctor-initializer 列表(除了在评论中)。它实际上看起来更像......算了,在评论中粘贴示例代码看起来不太好,它已经在我的答案中了。以上是关于不可复制的对象和值初始化:g++ vs msvc的主要内容,如果未能解决你的问题,请参考以下文章