c++ - 错误到底是什么以及错误地为可 move 和不可复制成员调用复制构造函数有什么解决方法

标签 c++ visual-c++ c++11 compiler-errors move-semantics

请考虑下面的代码,它在 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;
}

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

谢谢!

最佳答案

此代码不应编译。

问题在于您正在使用复制初始化,这可能需要(如果编译器没有忽略它)构造 LazyValue<Container> 类型的临时对象。 ,然后将其 move 到初始化对象 s 中.

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

The initialization that occurs in the form

T x = a;

as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization. [ Note: Copy-initialization may invoke a move (12.8). —end note ]

此外,根据第 8.5/16 段:

[...] Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type. The temporary is a prvalue. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see 12.2, 12.8.

暂时假设您的编译器不会省略复制/move (允许编译器这样做,但不要求这样做)。

您的类模板没有定义任何 move 构造函数,将选择隐式生成的复制构造函数来构造对象 s来自初始化右侧的 lambda 构造的临时值。

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

您应该使用直接初始化来代替:

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

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

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. —end note ]

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


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

关于c++ - 错误到底是什么以及错误地为可 move 和不可复制成员调用复制构造函数有什么解决方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14967167/

相关文章:

c++ - 流缓冲区实现的 std::endl 和 '\n' 之间的差异

c++ - Rvalue 成员确实也是 Rvalues 吗?

c++ - Tic Tac Toe C++算法调试帮助

c++ - VC++ fatal error LNK1168 : cannot open filename. exe for writing

c++ - 使用 MSVC 2013 初始化 `static constexpr double`

C++将文件读入 vector

c++ - 不能在非 Boost 版本的 Asio 中使用 asio::placeholders::error

c++ - 在 C++ 中使用的奇怪声明

c++ - Priority_queue 中的多次插入和删除

c++ - 录制应用程序的音频输出