当<T> T 没有复制构造函数时,std::queue 的虚拟包装器不会编译
Virtual wrapper of std::queue<T> does not compile when T has no copy constructor
我使用的是C++11。在为std::queue<T>
编写简单包装并将其与没有复制构造函数的类一起使用时,我遇到了一个编译错误。
下面是一个片段来说明这个问题。
基本上,我有一个std::queue<T>
的包装器,它有两个用于push
的虚拟重载。
#include <queue>
#include <utility>
#include <future>
template <typename T>
class myqueue {
public:
myqueue() : q() {}
virtual ~myqueue() {}
// pushes a copy
virtual void push(const T& item) {
q.push(item);
}
// pushes the object itself (move)
virtual void push(T&& item) {
q.push(std::move(item));
}
private:
std::queue<T> q;
};
int main() {
// Thanks to Yakk for pointing out that I can reduce clutter by using one of std's non-copyable classes!
myqueue<std::packaged_task<int()>> q;
std::packaged_task<int()> t([]{return 42;});
q.push(std::move(t));
}
当我试图在我的Linux机器上编译这个(ICC 13.2,g++4.7.3,以及Ideone上的g++4.7.2:http://ideone.com/HwBhIX),编译器抱怨它无法实例化myqueue::push(const-nocopy&),因为nocopy的复制构造函数被删除了。
如果我从push
中删除virtual
修饰符,则编译良好(在我的机器和Ideone上)。
有人能帮我理解为什么会发生这种事吗?
附言:这是Ideone的错误堆栈:
In file included from /usr/include/c++/4.7/i486-linux-gnu/bits/c++allocator.h:34:0,
from /usr/include/c++/4.7/bits/allocator.h:48,
from /usr/include/c++/4.7/deque:62,
from /usr/include/c++/4.7/queue:61,
from prog.cpp:1:
/usr/include/c++/4.7/ext/new_allocator.h: In instantiation of ‘void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = std::packaged_task<int()>; _Args = {const std::packaged_task<int()>&}; _Tp = std::packaged_task<int()>]’:
/usr/include/c++/4.7/bits/stl_deque.h:1376:6: required from ‘void std::deque<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::packaged_task<int()>; _Alloc = std::allocator<std::packaged_task<int()> >; std::deque<_Tp, _Alloc>::value_type = std::packaged_task<int()>]’
/usr/include/c++/4.7/bits/stl_queue.h:212:9: required from ‘void std::queue<_Tp, _Sequence>::push(const value_type&) [with _Tp = std::packaged_task<int()>; _Sequence = std::deque<std::packaged_task<int()>, std::allocator<std::packaged_task<int()> > >; std::queue<_Tp, _Sequence>::value_type = std::packaged_task<int()>]’
prog.cpp:13:9: required from ‘void myqueue<T>::push(const T&) [with T = std::packaged_task<int()>]’
prog.cpp:28:1: required from here
/usr/include/c++/4.7/ext/new_allocator.h:110:4: error: use of deleted function ‘std::packaged_task<_Res(_ArgTypes ...)>::packaged_task(const std::packaged_task<_Res(_ArgTypes ...)>&) [with _Res = int; _ArgTypes = {}; std::packaged_task<_Res(_ArgTypes ...)> = std::packaged_task<int()>]’
In file included from prog.cpp:3:0:
/usr/include/c++/4.7/future:1337:7: error: declared here
myqueue
的标准复制构造函数调用nocopy
的复制构造函数。只需覆盖或=delete
复制构造函数和myqueue
的operator=
,就可以了。
这是因为gcc 4.7提供的c++11实现不完整。使用c++11时,必须始终使用最新的编译器版本。clang通常是最完整的,其次是gcc,还有一段距离后的icpc。
编辑后,代码不再编译(clang 3.2或gcc 4.8),因为这是错误的。当模板类myqueue<std::packaged_task<int()>>
被实例化时,非模板成员virtual void push(const T&)
也被实例化。然而,此成员调用T
的已删除副本构造函数,因此是非法的,错误。(它为非虚拟push(const T&)
编译是危险的,因为一旦你尝试使用该函数,就会出现错误。)
要使代码正常工作,必须避免这种情况。virtual
成员不能是模板(这将允许您通过SFINAE避免问题)。但您可以根据T
是否可复制来专门处理class myqueue<T>
。以下代码使用gcc 4.8编译(但不使用icpc 13或clang 3.2编译,后者有std::is_copy_constructible
的错误实现)。
#include <queue>
#include <type_traits>
#include <utility>
#include <future>
template <typename T, typename E=void> class myqueue;
template <typename T>
class myqueue<T, typename std::enable_if<std::is_copy_constructible<T>::value>::type>
{
std::queue<T> q;
public:
virtual ~myqueue() {}
// pushes a copy
virtual void push(const T& item) { q.push(item); }
// pushes the object itself (move)
virtual void push(T&& item) { q.push(std::move(item)); }
};
template <typename T>
class myqueue<T,typename std::enable_if<!std::is_copy_constructible<T>::value>::type>
{
std::queue<T> q;
public:
virtual ~myqueue() {}
// pushes the object itself (move)
virtual void push(T&& item) { q.push(std::move(item)); }
};
int main() {
std::packaged_task<int()> t([]{return 42;});
myqueue<std::packaged_task<int()>> q;
q.push(std::move(t));
}
当然,这与您的原始代码不太一样,因为不可复制的T
不会有virtual void push(const&T)
,但我认为这正是您想要的。或者,您可以在不可复制的T
的专门化中使冒犯性方法纯粹是虚拟的。
对不起,我不能为你修复icpc,但至少这个代码看起来是合法的C++11。
- 如何在c++17中制作一个模板包装器/装饰器
- 虚拟决赛作为安全
- std::vector的包装器,使数组的结构看起来像结构的数组
- PowerPC ppc64le上的Gcc Woverloaded虚拟错误
- 如何在c++迭代器类型中包装std::chrono
- 如何在C++中获得"静态纯虚拟"功能?
- 是否可以用"iostream"包装现有的TCP/OOpenSSL会话
- C++无法定义虚拟函数 OUTER 类和头文件
- 用pybind11包装C++抽象类时出错
- 为左值和右值的包装器实现C++范围
- C结构,其指针将被包装在unique_ptr中
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- c++\CLI dll包装器,用于调用c++类中的虚拟成员
- 创建一个允许轻松创建虚拟函数包装器函数的C++宏
- 提升 Python 包装的虚拟类 - 子类返回错误:与C++签名不匹配
- Boost.Python 包装器中的纯虚拟重载运算符
- 在托管包装器中"override"本机虚拟方法的最佳方法
- C++/CLI:如何用虚拟方法包装非托管类以进行托管并使用C#中的方法
- 当<T> T 没有复制构造函数时,std::queue 的虚拟包装器不会编译
- SWIG JAVA 如何使用 %interface 和纯虚拟方法包装C++多重继承