4

我看到 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 版)上都失败了,因为复制构造函数是私有的。

两个问题:

  1. 哪种行为符合标准?
  2. 关于如何在 gcc 中解决此问题的任何建议(并且要明确的是,将其更改T t;为不可接受的解决方案,因为这会破坏 POD 类型)。

PS 我在 boost::noncopyable 上看到了同样的问题。

4

4 回答 4

8

您在 MSVC 中看到的行为是一个扩展,尽管在下一页(强调我的)http://msdn.microsoft.com/en-us/library/0yw5843c.aspx上以迂回的方式记录了它:

等号初始化语法与函数样式语法不同,尽管生成的代码在大多数情况下是相同的。不同之处在于,当使用等号语法时,编译器必须表现得好像发生了以下事件序列:

  • 创建与正在初始化的对象相同类型的临时对象。
  • 将临时对象复制到对象。

在编译器可以执行这些步骤之前,必须可以访问构造函数。尽管在大多数情况下编译器可以消除临时创建和复制步骤,但不可访问的复制构造函数会导致等号初始化失败(在 /Za、/Ze(禁用语言扩展)下)。

boost::value_initialized如 litb 在对 Ben 的回答的评论中所指出的,请参阅 Ben Voigt 的回答,了解一种简化版本的解决方法。的文档对boost::value_initalized问题、解决方法以及各种编译器问题的一些陷阱进行了很好的讨论。

于 2010-04-19T23:45:37.617 回答
5

我认为不需要模板元编程。尝试

template <class T>
void doit()
{
    struct initer { T t; initer() : t() {} } inited;
    T& t = inited.t;
    ...
}
于 2010-04-20T01:22:02.933 回答
3

有§12.8/14:

如果隐式使用对象的复制构造函数或复制赋值运算符并且特殊成员函数不可访问,则程序是不正确的。

然后是§12.8/15:

当满足某些条件时,允许实现省略类对象的复制构造,即使对象的复制构造函数和/或析构函数具有副作用。

所以,问题是,如果实现省略了对复制构造函数的调用(显然允许这样做),是否实际使用了复制构造函数?

而且,根据第 3.2/2 节,答案是肯定的:

即使实现实际上省略了调用,也会使用复制构造函数。

于 2010-04-19T23:59:27.140 回答
0

您是否看到使用 /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(),则 MSVC 会生成另一个警告 (C4930),尽管 GCC 很乐意接受它而不会发出任何警告。

于 2010-04-20T00:21:17.977 回答