C ++类型擦除与模板复制和移动构造函数

时间:2012-12-11 21:03:32

标签: c++

我最近开始学习类型擦除。事实证明,这种技术可以大大简化我的生活。因此,我试图实现这种模式。但是,我遇到类型擦除类的复制和移动构造函数的一些问题。 现在,让我们首先看看代码,这是非常直接的

#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);,编译器仍然尝试使用移动构造函数。这是编译器的预期行为吗?我是否因为类型擦除模式而错过了解一般的东西?有人知道如何做到这一点吗?

2 个答案:

答案 0 :(得分:3)

从编译器错误可以看出,编译器正在尝试为Holder实例化T = B&类。这意味着该类将存储引用类型的成员,这会给您带来一些复制等问题。

问题在于T&&(对于推导出的模板参数)是一个通用引用,这意味着它将绑定到所有内容。对于B的r值,它会将T推导为B并绑定为r值引用,对于l值,它将推导T为{{} 1}}并使用引用折叠将B&解释为B& &&(对于B& l值,它会推导const BT并进行折叠) 。在您的示例中,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课程。

其他要考虑的要点: 为什么要为HolderErasure::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()参数ostd::forward<T>()它:使用std::move(o)可能会造成灾难性后果,以防你实际传递非const引用Erasure的构造函数。

我没有过多关注其他代码,据我所知,还有一些语义错误(例如,你需要某种形式的引用计数或clone()形式的Erasure包含指针,以及{{1}}中的资源控制(即复制构造函数,复制赋值和析构函数)。