Domanda

I have a simple situation where I have some uniform interface, such as this:

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

I have some templates that have the same interface but do not declare it virtual, for performance reasons. So I need to add one more layer that makes their function virtual:

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()
    }
};

Note that I replaced mb.Run() by printing its value. That is only for simplification, it does not affect the behavior I'm encountering here. But anyway, so far so good. Now to make things convenient, I have one more class that makes the interface automatically:

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;
    }
};

This acts as an automatic constructor of S and also as a simple managed pointer at the same time. I would use it as follows:

A a = instanceOfB();

That should call the template A::A<instanceOfB>() which in turn allocates a new S<instanceOfB> on heap and stores it in an A. Now I can call A->Run() which finally resolves to S->Run() and that would call instanceOfB::Run(), which is not virtual.

Instead what happens is that instanceOfB() gets first converted to A and then it dies, as there is no constructor that would take A (only A&). Note that this only happens in g++, Visual Studio 2008 and Visual C++ 6.0 both compile the code without problems. You can reproduce the behavior using:

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();
}

I have tried declaring the constructors as explicit, but that doesn't seem to help or I may be doing it wrong. If A was not managing the pointer, I could take value of const A& in constructor, so the whole problem would be solved. Is there another solution to this problem? C++11 is unfortunately NOT available.

I'm trying to implement efficient delegates. Basically I want to be able to do:

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

Ultimately, high performance (this will later serve for acceleration of some linear algebra functions) and user comfort (short AutoCallPointer p = Grab(fun)(parms) without writing template argument lists) are the main goals here.

EDIT:

The solution of @ecatmur is correct. As it is quite short, I will attempt to reiterate here. g++ correctly refuses to compile the code, as in A there is no copy-constructor that will take A (only A&). The template constructor will not be used in the case of copy initialization A a = instanceOfB().

We must provide a copy-constructor, taking const A&. Because of copy elision, a declaration of the constructor without a body is sufficient. This is however not a nice workarround.

It is better to declare the A::mi as mutable and change the existing A& constructor to take const A& (the copy-operator may also be changed). The fixed A looks like this:

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;
    }
};

This code compiles in both g++ and Microsoft's compilers (also in http://codepad.org/9FqUk0Fj).

È stato utile?

Soluzione

When you copy-initialize an object of class type, the copy constructor needs to be available, even if the copy is elided. g++ is correct to reject your program; your older versions of MSVC are incorrect to accept it.

You may be able to provide a declaration of a copy constructor without a definition, on the basis that calls to it will be elided or otherwise fail at link time. This could be somewhat confusing, though.

The most obvious solution is to use direct-initialization, which as you have already observed works fine.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top