我最近开始学习类型擦除。事实证明,这种技术可以大大简化我的生活。因此,我试图实现这种模式。但是,我遇到类型擦除类的复制和移动构造函数的一些问题。 现在,让我们首先看看代码,这是非常直接的
#include<iostream>
class A //first class
{
private:
double _value;
public:
//default constructor
A():_value(0) {}
//constructor
A(double v):_value(v) {}
//copy constructor
A(const A &o):_value(o._value) {}
//move constructor
A(A &&o):_value(o._value) { o._value = 0; }
double value() const { return _value; }
};
class B //second class
{
private:
int _value;
public:
//default constructor
B():_value(0) {}
//constructor
B(int v):_value(v) {}
//copy constructor
B(const B &o):_value(o._value) {}
//move constructor
B(B &&o):_value(o._value) { o._value = 0; }
//some public member
int value() const { return _value; }
};
class Erasure //the type erasure
{
private:
class Interface //interface of the holder
{
public:
virtual double value() const = 0;
};
//holder template - implementing the interface
template<typename T> class Holder:public Interface
{
public:
T _object;
public:
//construct by copying o
Holder(const T &o):_object(o) {}
//construct by moving o
Holder(T &&o):_object(std::move(o)) {}
//copy constructor
Holder(const Holder<T> &o):_object(o._object) {}
//move constructor
Holder(Holder<T> &&o):_object(std::move(o._object)) {}
//implements the virtual member function
virtual double value() const
{
return double(_object.value());
}
};
Interface *_ptr; //pointer to holder
public:
//construction by copying o
template<typename T> Erasure(const T &o):
_ptr(new Holder<T>(o))
{}
//construction by moving o
template<typename T> Erasure(T &&o):
_ptr(new Holder<T>(std::move(o)))
{}
//delegate
double value() const { return _ptr->value(); }
};
int main(int argc,char **argv)
{
A a(100.2344);
B b(-100);
Erasure g1(std::move(a));
Erasure g2(b);
return 0;
}
作为编译器,我在Debian测试系统上使用gcc 4.7。假设代码存储在名为terasure.cpp
的文件中,则构建会导致以下错误消息
$> g++ -std=c++0x -o terasure terasure.cpp
terasure.cpp: In instantiation of ‘class Erasure::Holder<B&>’:
terasure.cpp:78:45: required from ‘Erasure::Erasure(T&&) [with T = B&]’
terasure.cpp:92:17: required from here
terasure.cpp:56:17: error: ‘Erasure::Holder<T>::Holder(T&&) [with T = B&]’ cannot be overloaded
terasure.cpp:54:17: error: with ‘Erasure::Holder<T>::Holder(const T&) [with T = B&]’
terasure.cpp: In instantiation of ‘Erasure::Erasure(T&&) [with T = B&]’:
terasure.cpp:92:17: required from here
terasure.cpp:78:45: error: no matching function for call to ‘Erasure::Holder<B&>::Holder(std::remove_reference<B&>::type)’
terasure.cpp:78:45: note: candidates are:
terasure.cpp:60:17: note: Erasure::Holder<T>::Holder(Erasure::Holder<T>&&) [with T = B&]
terasure.cpp:60:17: note: no known conversion for argument 1 from ‘std::remove_reference<B&>::type {aka B}’ to ‘Erasure::Holder<B&>&&’
terasure.cpp:58:17: note: Erasure::Holder<T>::Holder(const Erasure::Holder<T>&) [with T = B&]
terasure.cpp:58:17: note: no known conversion for argument 1 from ‘std::remove_reference<B&>::type {aka B}’ to ‘const Erasure::Holder<B&>&’
terasure.cpp:54:17: note: Erasure::Holder<T>::Holder(const T&) [with T = B&]
terasure.cpp:54:17: note: no known conversion for argument 1 from ‘std::remove_reference<B&>::type {aka B}’ to ‘B&’
似乎对于Erasure g2(b);
,编译器仍然尝试使用移动构造函数。这是编译器的预期行为吗?我是否因为类型擦除模式而错过了解一般的东西?有人知道如何做到这一点吗?
答案 0 :(得分:3)
从编译器错误可以看出,编译器正在尝试为Holder
实例化T = B&
类。这意味着该类将存储引用类型的成员,这会给您带来一些复制等问题。
问题在于T&&
(对于推导出的模板参数)是一个通用引用,这意味着它将绑定到所有内容。对于B
的r值,它会将T
推导为B
并绑定为r值引用,对于l值,它将推导T
为{{} 1}}并使用引用折叠将B&
解释为B& &&
(对于B&
l值,它会推导const B
为T
并进行折叠) 。在您的示例中,const B&
是一个可修改的l值,使得构造函数采用b
(推导为T&&
)与B&
(推导为const T&
更好匹配1}})一个。这也意味着const B&
构造函数不是必需的Erasure
(与const T&
不同,因为Holder
没有为该构造函数推导出来。)
解决这个问题的方法是在创建holder类时从类型中去除引用(可能是const,除非你想要一个const成员)。您还应该使用T
而不是std::forward<T>
,因为如上所述,构造函数也绑定到l值,并且从这些值移动可能是一个坏主意。
std::move
您的 template<typename T> Erasure(T&& o):
_ptr(new Holder<typename std::remove_cv<typename std::remove_reference<T>::type>::type>(std::forward<T>(o))
{}
类中还有另一个错误,它不会被编译器捕获:您将Erasure
存储在指向堆分配内存的原始指针中,但是没有自定义析构函数可以删除它也不是复制/移动/分配的自定义处理(三/五规则)。解决这个问题的一个选择是实现这些操作(或使用Holder
禁止不重要的操作)。然而这有点单调乏味,所以我的个人建议不是手动管理内存,而是使用=delete
进行内存管理(不会给你复制能力,但如果你想要,你首先需要扩展你无论如何克隆std::unique_ptr
课程。
其他要考虑的要点:
为什么要为Holder
,Erasure::Holder<T>
和A
实现自定义复制/移动构造函数?默认值应该非常精细,不会禁用生成移动赋值运算符。
另一点是,B
存在问题,因为它会与复制/移动构造函数竞争(Erasure(T &&o)
可以绑定到T&&
,这比Èrasure&
更好。和const Erasure&
)。为避免这种情况,您可以使用Erasure&&
检查enable_if
的类型,为您提供类似的内容:
Erasure
答案 1 :(得分:1)
您的问题是类型T
被推断为构造函数采用通用引用的引用。你想要使用以下内容:
#include <type_traits>
class Erasure {
....
//construction by moving o
template<typename T>
Erasure(T &&o):
_ptr(new Holder<typename std::remove_reference<T>::type>(std::forward<T>(o)))
{
}
};
也就是说,您需要删除从T
推导出的任何引用(也可能是任何cv限定符,但修正不会这样做)。然后你不希望std::move()
参数o
而std::forward<T>()
它:使用std::move(o)
可能会造成灾难性后果,以防你实际传递非const
引用Erasure
的构造函数。
我没有过多关注其他代码,据我所知,还有一些语义错误(例如,你需要某种形式的引用计数或clone()
形式的Erasure
包含指针,以及{{1}}中的资源控制(即复制构造函数,复制赋值和析构函数)。