请考虑下面的代码,它在VS2012中编译,但在VS2010中失败并出现错误
1>------ Build started: Project: testconstinit, Configuration: Debug Win32 ------
1> testconstinit.cpp
1>c:program files (x86)microsoft visual studio 10.0vcincludexmemory(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.0vcincludememory(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.0vcincludexmemory(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.0vcincludexmemory(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.0vcincludevector(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.0vcincludevector(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:userszadiriondocumentsvisual studio 2010projectstestconstinittestconstinittestconstinit.cpp(34) : see reference to class template instantiation 'std::vector<_Ty>' being compiled
1> with
1> [
1> _Ty=Movable
1> ]
1> c:userszadiriondocumentsvisual studio 2010projectstestconstinittestconstinittestconstinit.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;
}
任何人都可以指出提交给Microsoft的错误的链接,或者解释编译器错误实际上是什么,我试图了解代码的哪一部分到底困扰着编译器。另外,我们对此有什么解决方法?
谢谢!
此代码不应编译。
问题在于您正在使用复制初始化,这可能需要(如果编译器没有省略它(构造一个类型的临时对象 LazyValue<Container>
,然后将其移动到初始化的对象s
中。
从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 非限定版本的临时版本。临时是原则。然后,调用的结果(构造函数情况的临时结果(用于根据上述规则直接初始化作为复制初始化目标的对象。在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除此直接初始化中固有的复制;参见 12.2、12.8。
让我们暂时假设您的编译器不会省略复制/移动(允许编译器这样做,但不是必需的(。
您的类模板没有定义任何移动构造函数,并且将选择隐式生成的复制构造函数来构造对象s
从初始化右侧的 lambda 构造的临时对象。
不幸的是,你的类有一个类型为 Container
的成员变量,它是一个不可复制元素的容器。因此,隐式生成的复制构造的实例化将失败,这解释了您得到的错误。
应改用直接初始化:
const LazyValue<Container> s([]() -> Container {
return ListOfRValues<Movable>()
(Movable(new int) )
(Movable(new int) )
(Movable(new int) );
});
现在让我们考虑编译器确实选择省略复制/移动的情况。C++11标准中对这种行为有要求,来自第12.8/32段:
当满足或将满足复制操作省略的条件(源对象是函数参数,并且要复制的对象由左值指定(时,首先执行重载解析以选择复制的构造函数,就像对象由右值指定一样。如果重载解析失败,或者所选构造函数的第一个参数的类型不是对对象类型的右值引用(可能符合 cv(,则会再次执行重载解析,并将对象视为左值。[ 注意:无论是否会发生复制检测,都必须执行此两阶段重载解析。它确定在未执行 elision 时要调用的构造函数,并且即使省略了调用,也必须可以访问所选构造函数。—尾注 ]
此处的关键术语是可访问的。隐式生成的复制构造函数的实例化无法成功,因为要复制的对象包含不可复制的子对象;这必然使复制构造函数无法访问,因为它永远无法实例化。因此,符合要求的编译器应拒绝编译代码,我相信这符合VS2012中的错误。
PS:另外,请注意您违反了所谓的三法则(除了有一个重载的复制赋值运算符,它不返回任何内容,而它可能应该返回*this
(。