C ++ несколько интерфейсов, которые отличаются только типом возвращаемого значения? - PullRequest
0 голосов
/ 15 января 2019

Проведение эксперимента по переводу .NET IL в C ++ в удобочитаемой форме.

Вот проблема: C # позволяет разрешать несколько интерфейсов с одним и тем же именем метода, которые отличаются только типом возвращаемого значения. C ++, похоже, не поддерживает это, однако делает невозможным разрешение двух интерфейсов с помощью таблицы vTable (или я ошибаюсь?).

Я нашел способ тиражировать подход C # в C ++, используя шаблоны, но мне интересно, есть ли способ, который не требует шаблонов, который решает ту же проблему? Шаблоны многословны, и я ' Я бы предпочел не использовать их для каждого типа интерфейса, если это возможно.

Вот версия C ++.

template<typename T>
class IMyInterface
{
    public: short (T::*Foo_IMyInterface)() = 0;
};

template<typename T>
class IMyInterface2
{
    public: int (T::*Foo_IMyInterface2)() = 0;
};

class MyClass : public IMyInterface<MyClass>, public IMyInterface2<MyClass>
{
    public: MyClass()
    {
        Foo_IMyInterface = &MyClass::Foo;
        Foo_IMyInterface2 = &MyClass::IMyInterface2_Foo;
    }

    public: virtual short Foo()
    {
        return 1;
    }

    private: int IMyInterface2_Foo()
    {
        return 1;
    }
};

class MyClass2 : public MyClass
{
    public: virtual short Foo() override
    {
        return 2;
    }
};

void InvokeFoo(IMyInterface<MyClass>* k)
{
    (((MyClass*)k)->*k->Foo_IMyInterface)();
}

void main()
{
    auto a = new MyClass2();
    InvokeFoo(a);
}

Вот справочный источник C #, на котором основан C ++.

interface IMyInterface
{
    short Foo();
}

interface IMyInterface2
{
    int Foo();
}

class MyClass : IMyInterface, IMyInterface2
{
    public virtual short Foo()
    {
        return 1;
    }

    int IMyInterface2.Foo()
    {
        return 1;
    }
}

class MyClass2 : MyClass
{
    public override short Foo()
    {
        return 2;
    }
}

namespace CSTest
{
    class Program
    {
        static void InvokeFoo(IMyInterface k)
        {
            k.Foo();
        }

        static void Main(string[] args)
        {
            var a = new MyClass2();
            InvokeFoo(a);
        }
    }
}

Этот метод C ++ не работает ниже , но хотелось бы, чтобы он работал (это больше, чем я собираюсь).

class IMyInterface
{
    public: virtual short Foo() = 0;
};

class IMyInterface2
{
    public: virtual int Foo() = 0;
};

class MyClass : public IMyInterface, public IMyInterface2
{
    public: virtual short Foo()
    {
        return 1;
    }

    private: int IMyInterface2::Foo()// compiler error
    {
        return 1;
    }
};

class MyClass2 : public MyClass
{
    public: virtual short Foo() override
    {
        return 2;
    }
};

void InvokeFoo(IMyInterface* k)
{
    k->Foo();
}

void main()
{
    auto a = new MyClass2();
    InvokeFoo(a);
}

Ответы [ 4 ]

0 голосов
/ 16 января 2019

Если вам нужен код C ++, который по сути будет похож на ваш пример C #, я бы порекомендовал вам по существу добавить дополнительный промежуточный класс MyClass_IMyInterface2 с единственной целью переименовать методы, которые явно реализованы в коде C #.

Таким образом, у вас будет что-то вроде:

class IMyInterface2
{
public: 
    virtual int Foo() = 0;
};

class MyClass_IMyInterface2 : public IMyInterface2
{
protected:
    int Foo() final { return IMyInterface2_Foo(); }
    virtual int IMyInterface2_Foo() = 0; 
};

class MyClass : public IMyInterface, public MyClass_IMyInterface2...

Кстати, вам нужно добавить дополнительный класс, только если у вас есть конфликты, и вам нужно только переименовать, если это так.

Предполагая, что целью было бы имитировать код C #, я, вероятно, делал бы это каждый раз, когда явно реализован интерфейсный метод.

Вы также можете настроить видимость членов по своему усмотрению.

0 голосов
/ 15 января 2019

Проблема в том, что вы не можете перегрузить только на основе типа возвращаемого значения.

См.

Последний поток stackoverflow указывает, что перегрузка возможна с использованием операторов.

struct func {
    operator string() { return "1"; }
    operator int() { return 2; }
};

int main() {
    int x = func(); // calls int version
    string y = func(); // calls string version
    double d = func(); // calls int version
    cout << func() << endl; // calls int version
    func(); // calls neither
}

Вы не можете назвать их, хотя это быстро превратится в беспорядок для работы.

Список аргументов должен измениться. Виктор Падюро предложил использовать void возвращаемые типы и передавать тип значения в качестве ссылки, чтобы установить значение в методе, который будет работать. Вы также можете изменить имя метода для разных типов.

class my_interface
{
public: 
    virtual short foo_short() = 0;
};

class my_interface2
{
public: 
    virtual int foo_int() = 0;
};

class my_class : public my_interface, public my_interface2
{
public: 
    short foo_short() override
    {
        return 1;
    }

    int foo_int() override
    {
        return 1;
    }
};

class my_class2 : public my_class
{
public: 
    virtual short foo_short() override
    {
        return 2;
    }
};

void InvokeFoo(my_interface* k)
{
    short result = k->foo_short();
    std::cout << result << std::endl;
}

void main()
{
    auto a = new my_class2();
    InvokeFoo(a);
}
0 голосов
/ 16 января 2019

Лучшее - это: не имеющий одинаковых имен функций в обоих интерфейсах , создание выходных параметров возвращаемых значений безусловно, является интересной альтернативой. Для полноты: давайте предположим, что мы не можем изменить интерфейсы базовых классов, возможно, из-за того, что они поступают из разных библиотек & ndash; и нам все еще нужно переопределить.

Ну, во-первых: наследование обоих интерфейсов вообще может быть сомнительным проектом, очень вероятно, что вы нарушите принцип единственной ответственности .

Теперь давайте предположим, что нам нужно в любом случае. Тогда возможный обходной путь (он не соответствует моему собственному определению «элегантности» - но по крайней мере ...) - это промежуточные классы:

class BaseA { virtual ~BaseA(); virtual void foo(); };
class BaseB { virtual ~BaseB(); virtual int foo(); };

class DerivedA : public BaseA { void foo() override; };
class DerivedB : public BaseB { int foo() override; };

class Derived : public DerivedA, public DerivedB { };

Теперь Derived может служить как BaseA , так и BaseB и все еще имеет переопределенные оба (в данном случае) варианта foo, хотя и косвенно.

Если вы намереваетесь продолжить наследование от Derived, все еще позволяя переопределить любой вариант функции foo, то каждое переопределение в DerivedA и DerivedB само вызовет (новую) виртуальную функцию fooA и fooB соответственно:

class DerivedA : public BaseA
{
public:
    void foo() final // better than override:
                     // prevents breaking the pattern again
    { fooA() };
protected:
    virtual void fooA() = 0;
};
class DerivedB : public BaseB
{
public:
    int foo() final { return fooB() };
protected:
    virtual int fooB() = 0;
};

Этот же трюк позволяет переопределениям использовать члены обеих баз и, безусловно, является чистым подходом для решения этой проблемы, однако он имеет некоторый компромисс, поскольку вызовы виртуальных функций не приходят бесплатно (поиск в таблице, затем фактический вызов функции) & ndash; поэтому, если производительность важнее (подумайте, если она действительно !), вы можете просто реализовать переопределения в DerivedA и DerivedB напрямую.

0 голосов
/ 15 января 2019

У меня есть решение, которое может работать. Это не идеально, но это способ обойти проблему, если вы портируете.

Вместо того, чтобы вызывать int foo(), вы можете вызвать void foo(int& out) таким образом, чтобы поместить возвращаемый тип в вызывающую часть функции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...