C ++构造函数中的不需要的隐式转换

时间:2014-04-28 14:07:13

标签: c++ templates constructor

我有一个简单的情况,我有一些统一的界面,例如:

class I {
public:
    virtual void Run() = 0;
};

出于性能原因,我有一些具有相同界面但未将其声明为虚拟的模板。所以我需要再添加一个使其功能成为虚拟的层:

template <class B>
class S : public I {
    B mb;

public:
    S(B b)
        :mb(b)
    {}

    virtual void Run()
    {
        std::cout << mb << std::endl; // or mb.Run()
    }
};

请注意,我通过打印其值来替换mb.Run()。这只是为了简化,它不会影响我在这里遇到的行为。但无论如何,到目前为止一切都那么好。现在为了方便起见,我还有一个自动创建界面的类:

class A {
    I *mi;

public:
    A()
        :mi(0)
    {}

    template <class B>
    A(B b)
        :mi(new S<B>(b))
    {}

    A(A &a)
        :mi(a.mi)
    {
        a.mi = 0;
    }

    template <class B>
    A &operator =(B b)
    {
        delete mi;
        mi = new S<B>(b);
        return *this;
    }

    A &operator =(A &a)
    {
        delete mi;
        mi = a.mi;
        a.mi = 0;
        return *this;
    }

    I *operator ->()
    {
        assert(mi);
        return mi;
    }
};

它充当S的自动构造函数,同时也作为一个简单的托管指针。我会用它如下:

A a = instanceOfB();

那应该调用模板A::A<instanceOfB>(),模板又在堆上分配新的S<instanceOfB>并将其存储在A中。现在,我可以致电A->Run(),最终解析为S->Run(),这将调用instanceOfB::Run(),这不是虚拟的。

相反,发生的事情是instanceOfB()首先转换为A然后它就会死亡,因为没有构造函数需要A(仅A&)。请注意,这只发生在g ++,Visual Studio 2008和Visual C ++ 6.0中都可以毫无问题地编译代码。您可以使用以下命令重现行为:

void Test()
{
    A a = 4; // error: no matching function for call to "A::A(A)"
    //A a; a = 4; // works
    //A a(4); // works
    a->Run();
}

我已经尝试将构造函数声明为显式,但这似乎没有帮助,或者我可能做错了。如果A没有管理指针,我可以在构造函数中取值const A&,这样就可以解决整个问题。还有另一个解决这个问题的方法吗?很遗憾,C ++ 11无法使用。

我正在尝试实现高效的委托。基本上我希望能够做到:

int myFunction(int, float);
StaticCallCtx<int, MakeTypelist(int, float)> ctx = Grab(&myFunction)(1, 2.3f);

// ctx.Run() calls the function, with the specified arguments
// it's *not* virtual (compiles to just a few instructions so I
// don't want to spoil it by adding a vfptr)

AutoCallPointer p = ctx;
// or directly AutoCallPointer p = Grab(&myFunction)(1, 2.3f);
// wraps StaticCallCtx, has ->Run() as well, this time there
// is the price of calling the virtual function

最终,高性能(这将在以后用于加速某些线性代数函数)和用户舒适度(短AutoCallPointer p = Grab(fun)(parms)而不编写模板参数列表)是这里的主要目标。

修改

@ecatmur的解决方案是正确的。由于时间很短,我将在此重申。 g ++正确拒绝编译代码,如A中没有复制构造函数需要A(仅A&)。在复制初始化A a = instanceOfB()的情况下,模板构造函数将

我们必须提供一个复制构造函数,并const A&。由于复制省略,没有正文的构造函数的声明就足够了。然而,这不是一个很好的工作环境。

最好将A::mi声明为mutable并将现有的A&构造函数更改为const A&(复制运算符也可能会更改)。固定A看起来像这样:

class A {
    mutable I *mi;

public:
    A()
        :mi(0)
    {}

    template <class B>
    A(B b)
        :mi(new S<B>(b))
    {}

    A(const A &a)
        :mi(a.mi)
    {
        a.mi = 0;
    }

    template <class B>
    A &operator =(B b)
    {
        delete mi;
        mi = new S<B>(b);
        return *this;
    }

    A &operator =(const A &a)
    {
        delete mi;
        mi = a.mi;
        a.mi = 0;
        return *this;
    }

    I *operator ->()
    {
        assert(mi);
        return mi;
    }
};

此代码在g ++和Microsoft的编译器中编译(也在http://codepad.org/9FqUk0Fj中)。

1 个答案:

答案 0 :(得分:2)

复制初始化类类型的对象时,复制构造函数需要可用,即使复制被省略也是如此。 g ++拒绝你的程序是正确的;您的旧版本的MSVC不正确接受它。

您可以在没有定义的情况下提供复制构造函数的声明,因为在链接时将忽略对它的调用或以其他方式失败。但这可能有些令人困惑。

最明显的解决方案是使用直接初始化,正如您已经观察到的那样正常。