5

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

#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.cppbuild 的文件中会导致以下错误消息

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

4

2 回答 2

3

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

问题在于T&&(对于推导的模板参数)是一个通用引用,这意味着它将绑定到所有内容。对于它的 r 值,B它将推断T为 beB并绑定为 r 值引用,对于 l 值,它将推断T为 beB&并使用引用折叠来解释B& &&B&(对于const Bl 值,它将推断Tconst B&并进行折叠) . 在您的示例b中,是一个可修改的左值,使构造函数采用T&&(推断为)比(推断为B&)更好的匹配。这也意味着构造函数并不是真正需要的(与由于const T&const B&Erasureconst T&HolderT没有为该构造函数推断)。

对此的解决方案是在创建持有者类时从类型中删除引用(可能还有常量,除非您想要一个 const 成员)。您还应该使用std::forward<T>而不是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)。然而这有点乏味,所以我个人的建议是不要手动管理内存,而是使用 astd::unique_ptr进行内存管理(不会给你复制能力,但如果你想要你首先需要扩展你Holder的类以进行克隆) .

其他要考虑的要点:为什么要为 和 实现自定义复制/移动Erasure::Holder<T>构造A函数B?默认值应该很好,并且不会禁用移动赋值运算符的生成。

另一点是Erasure(T &&o)有问题的,因为它将与复制/移动构造函数竞争(T&&可以绑定到Èrasure&哪个是更好的匹配然后两者const Erasure&Erasure&&。为避免这种情况,您可以使用enable_if检查类型Erasure,为您提供与此类似的内容:

    template<typename T, typename Dummy = typename std::enable_if<!std::is_same<Erasure, std::remove_reference<T>>::value>::type>
    Erasure(T&& o):
        _ptr(new Holder<typename std::remove_cv<typename std::remove_reference<T>::type>::type>(std::forward<T>(o))
   {}
于 2012-12-11T21:32:20.327 回答
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()包含指针的 int 形式,以及资源控制(即复制构造函数、复制赋值和析构函数)在Erasure.

于 2012-12-11T21:32:27.717 回答