2

请考虑下面的代码,它在 VS2012 中编译但在 VS2010 中失败并出现错误

    1>------ Build started: Project: testconstinit, Configuration: Debug Win32 ------
1>  testconstinit.cpp
1>c:\program files (x86)\microsoft visual studio 10.0\vc\include\xmemory(48): error C2248: 'std::unique_ptr<_Ty>::unique_ptr' : cannot access private member declared in class 'std::unique_ptr<_Ty>'
1>          with
1>          [
1>              _Ty=int
1>          ]
1>          c:\program files (x86)\microsoft visual studio 10.0\vc\include\memory(2347) : see declaration of 'std::unique_ptr<_Ty>::unique_ptr'
1>          with
1>          [
1>              _Ty=int
1>          ]
1>          c:\program files (x86)\microsoft visual studio 10.0\vc\include\xmemory(197) : see reference to function template instantiation 'void std::_Construct<std::unique_ptr<_Ty>,const std::unique_ptr<_Ty>&>(_Ty1 *,_Ty2)' being compiled
1>          with
1>          [
1>              _Ty=int,
1>              _Ty1=std::unique_ptr<int>,
1>              _Ty2=const Movable &
1>          ]
1>          c:\program files (x86)\microsoft visual studio 10.0\vc\include\xmemory(196) : while compiling class template member function 'void std::allocator<_Ty>::construct(std::unique_ptr<int> *,const _Ty &)'
1>          with
1>          [
1>              _Ty=Movable
1>          ]
1>          c:\program files (x86)\microsoft visual studio 10.0\vc\include\vector(421) : see reference to class template instantiation 'std::allocator<_Ty>' being compiled
1>          with
1>          [
1>              _Ty=Movable
1>          ]
1>          c:\program files (x86)\microsoft visual studio 10.0\vc\include\vector(481) : see reference to class template instantiation 'std::_Vector_val<_Ty,_Alloc>' being compiled
1>          with
1>          [
1>              _Ty=Movable,
1>              _Alloc=std::allocator<Movable>
1>          ]
1>          c:\users\zadirion\documents\visual studio 2010\projects\testconstinit\testconstinit\testconstinit.cpp(34) : see reference to class template instantiation 'std::vector<_Ty>' being compiled
1>          with
1>          [
1>              _Ty=Movable
1>          ]
1>          c:\users\zadirion\documents\visual studio 2010\projects\testconstinit\testconstinit\testconstinit.cpp(81) : see reference to class template instantiation 'LazyValue<T>' being compiled
1>          with
1>          [
1>              T=Container
1>          ]
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

编码:

#include "stdafx.h"
#include <vector>
#include <memory>
#include <functional>
#include <deque>


using namespace std;



typedef std::unique_ptr<int> Movable;
typedef vector<Movable> Container;

typedef vector<Movable> (*MakeType)();


template <class T, class Initializer = function<T(void)> >
struct LazyValue
{
  LazyValue(Initializer aInit) : mInit(aInit) {}


  void Init() const
  {
    m = mInit();
  }

private:

  mutable T m; // <-- compiler error at this line
  Initializer mInit;

  LazyValue operator=(const LazyValue & aOther)
  {

  }
};

template <class T>
struct GenericList
{
    std::deque<T> mValues;

    GenericList(){}

    GenericList & operator()(T && aValue)
    {
        mValues.push_back(std::move(aValue));
        return *this;
    }

    template <class Container>
    operator Container()
    {
        auto it = mValues.begin();
        auto endIt = mValues.end();

        Container c;

        for ( ; it != endIt; it++ )
        {
            c.push_back(std::move(*it));
        }
        return std::move(c);
    }
};

template <class T>
GenericList<T> ListOfRValues()
{
    return GenericList<T>();
}

int _tmain(int argc, _TCHAR* argv[])
{

  const LazyValue<Container> s = []()->Container{
      return ListOfRValues<Movable>()
        (Movable(new int) )
        (Movable(new int) )
        (Movable(new int) );
  };

  return 0;
}

任何人都可以指出提交给微软的错误的链接,或者解释编译器错误实际上是什么,我试图了解代码的哪一部分到底是困扰编译器。另外,我们有什么解决方法?

谢谢!

4

1 回答 1

5

此代码不应编译。

问题在于您正在使用copy-initialization,这可能需要(如果编译器没有省略它)构造一个临时对象 type LazyValue<Container>,然后将其移动到初始化 objects中。

来自 C++11 标准的第 8.5/14 段:

表单中发生的初始化

T x = a;

以及在参数传递、函数返回、抛出异常 (15.1)、处理异常 (15.3) 和聚合成员初始化 (8.5.1) 中称为复制初始化。[注意:复制初始化可能会调用移动(12.8)。——尾注]

此外,根据第 8.5/16 段:

[...] 否则(即,对于剩余的复制初始化情况),枚举可以从源类型转换为目标类型或(当使用转换函数时)转换为其派生类的用户定义转换序列如 13.3.1.4 中所述,通过重载决议 (13.3) 选择最佳的。如果转换无法完成或不明确,则初始化格式错误。以初始化表达式作为参数调用所选函数;如果函数是构造函数,则调用初始化目标类型的 cv 非限定版本的临时版本。临时是prvalue。然后根据上述规则,调用的结果(对于构造函数的情况是临时的)用于直接初始化作为复制初始化目标的对象。在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除这种直接初始化中固有的复制;见 12.2、12.8。

让我们暂时假设您的编译器没有忽略复制/移动(编译器是允许的,但不是必需的)。

您的类模板没有定义任何移动构造函数,并且隐式生成的复制构造函数将被选择用于s从初始化右侧的 lambda 构造的临时构造对象。

不幸的是,您的类有一个 type 的成员变量Container,它是不可复制元素的容器。因此,隐式生成的复制构造的实例化将失败,这解释了您遇到的错误。

您应该改用直接初始化

const LazyValue<Container> s([]() -> Container {
  return ListOfRValues<Movable>()
    (Movable(new int) )
    (Movable(new int) )
    (Movable(new int) );
});

现在让我们考虑编译器确实选择省略复制/移动的情况。C++11 标准对此行为有要求,来自第 12.8/32 段:

当满足或将满足复制操作的省略标准时,除了源对象是函数参数,并且要复制的对象由左值指定之外,选择复制的构造函数的重载决策是首先执行好像对象是由右值指定的。如果重载决议失败,或者如果所选构造函数的第一个参数的类型不是对对象类型的右值引用(可能是 cv 限定的),则再次执行重载决议,将对象视为左值。[注意:无论是否会发生复制省略,都必须执行此两阶段重载解析。它确定如果不执行省略则调用的构造函数,并且选择的构造函数必须是可访问的即使呼叫被省略。——尾注]

这里的关键术语是可访问的。隐式生成的复制构造函数的实例化无法成功,因为要复制的对象包含不可复制的子对象;这必然使复制构造函数不可访问,因为它永远不能被实例化。因此,符合标准的编译器将拒绝编译代码,我相信这符合 VS2012 中的错误。


PS:另外,请注意您违反了所谓的三法则(除了有一个重载的复制赋值运算符不返回任何内容,而它可能应该返回*this)。

于 2013-02-19T21:08:15.867 回答