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

C++ Unwanted implicit conversion in constructor

本文关键字:转换 不需要 构造函数 C++      更新时间:2023-10-16

我有一个简单的情况,我有一些统一的接口,比如:

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)。

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

您可以在没有定义的情况下提供副本构造函数的声明,因为对它的调用将被忽略或在链接时失败。不过,这可能有些令人困惑。

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