Реализация интерфейсов в C ++ - PullRequest
9 голосов
/ 03 января 2012

Я обычно программирую на C #, но пытаюсь немного поработать на C ++ и немного пытаюсь реализовать интерфейсы на C ++.

В C # я бы сделал что-то вроде этого:

class Base<T>
{
    public void DoSomething(T value)
    {
        // Do something here
    }
}

interface IDoubleDoSomething
{
    void DoSomething(double value);
}

class Foo : Base<double>, IDoubleDoSomething
{
}

В C ++ я реализовал это так:

template <class T>
class Base
{
public:
    virtual void DoSomething(T value)
    {
        // Do something here
    }
};

class IDoubleDoSomething
{
public:
    virtual void DoSomething(double value) = 0;
};

class Foo : public Base<double>, public IDoubleDoSomething
{
};

Проблема в том, что я не могу создать экземпляр Foo, потому что он абстрактный (не реализует DoSomething). Я понимаю, что могу реализовать DoSomething и просто вызвать метод на Base, но я надеялся, что есть лучший способ сделать это. У меня есть другие классы, которые наследуют от базы с другими типами данных, и у меня есть другие классы, которые наследуют от IDoubleDoSomething, которые не используют Base.

Любая помощь приветствуется.

Ответы [ 6 ]

2 голосов
/ 03 января 2012

Вы можете передать второй параметр шаблона в Base, интерфейс, который он должен реализовать:

template <typename T, class Interface>
class Base : public Interface { ... };

class Foo : public Base<double, IDoubleDoSomething> { ... };

Для получения дополнительных бонусных баллов вы можете шаблонизировать IDoubleDoSomething (например, IDoSomething<double>) или использовать класс черт, чтобы отобразить тип T на соответствующий интерфейс.

2 голосов
/ 03 января 2012

Дело в том, что Base::DoSomething и IWhatever::DoSomething - это две не связанные функции (даже если бы в этом не было чисто виртуальных функций, вы не смогли бы вызвать DoSomething для объекта Foo, тем не мение). Base необходимо наследовать от IWhatever, чтобы это работало.

Тем не менее, спросите себя, действительно ли вам это нужно. Общее программирование с помощью шаблонов (и концепций, которые похожи на интерфейсы - см. Boost.ConceptCheck ) обычно является лучшим решением в C ++, чем полиморфизм подтипа времени выполнения.

2 голосов
/ 03 января 2012

В C ++ чисто виртуальные функции всегда должны быть переопределены в производном классе;они не могут наследовать переопределения от других базовых классов.Если вам нужен динамический полиморфизм, я не думаю, что есть разумная альтернатива написанию функции в Foo, которая вызывает функцию Base.Обратите внимание, что функция Base не обязательно должна быть виртуальной.

В зависимости от того, как вы используете эти классы (в частности, известен ли реальный тип каждого экземпляра интерфейса во время компиляции),вы можете использовать статический полиморфизм для внедрения вашего конкретного класса реализации в его пользователя в качестве параметра шаблона;например:

// No explicit interface specification with static polymorphism
class Foo : public Base<double>
{
    // Inherits DoSomething(double)
};

// Template can used with any class that impelements a 
// "DoSomething" function that can accept a double.
template <class DoubleDoSomething>
void DoSomethingWithDouble(DoubleDoSomething & interface, double value)
{
    interface.DoSomething(value);
}

// This particular specialisation uses a "Foo" object.
Foo foo;
DoSomethingWithDouble(foo, 42);
2 голосов
/ 03 января 2012

Рассмотрим следующий код c ++

class Foo
{
public:
    void DoSomething1(){}
};

template<typename t>
void MethodExpectsDosomething1( t f )
{
    f.DoSomething1();
}

template<typename t>
void MethodExpectsDosomething2( t f )
{
    f.DoSomething2();
}

int main()
{
    Foo f;
    MethodExpectsDosomething1<Foo>( f );

    MethodExpectsDosomething2<Foo>( f );

    return 0;
}

В C ++ вы можете использовать Foo без реализации IDoSomething1 и IDoSomething2.Второй метод MethodExpectsDosomething2 просто не удастся скомпилировать, так как Foo не имеет метода DoSomething2.

В C # такая конструкция невозможна и вынуждает вас иметь интерфейс IDoSomething1 и IDoSomething2 и указывать это как ограничение типа .

Так, может быть, вам нужно взглянуть на свой код и посмотреть, нужны ли такие интерфейсы вообще?

1 голос
/ 03 января 2012

Как уже отмечали другие, две функции в базовых классах не связаны (несмотря на то, что имеют одинаковые имена и типы аргументов), поскольку у них нет общего базового класса. Если вы хотите, чтобы они были связаны, вам нужно дать им общий базовый класс.

Кроме того, в общем, если вы хотите, чтобы множественное наследование работало должным образом, вам нужно объявить свои не приватные базовые классы как virtual. В противном случае, если у вас когда-нибудь будут общие базовые классы (как вам часто требуется для этого стиля кода), произойдут плохие вещи.

Итак, вы можете заставить свой пример работать следующим образом:

template <class T>
class IDoSomething {
public:
    virtual void DoSomething(T value) = 0;
};

template <class T>
class Base : public virtual IDoSomething<T>
{
public:
    virtual void DoSomething(T value)
    {
        // Do something here
    }
};

class IDoubleDoSomething : public virtual IDoSomething<double>
{
};

class Foo : public virtual Base<double>, public virtual IDoubleDoSomething
{
};
0 голосов
/ 03 января 2012

Если все остальное в порядке, это должно работать.

class Foo : public Base<double>
{
};

В вашем исходном коде Foo будет иметь 2 пустых метода DoSomething (двойное значение) (1 является абстрактным). Это будет базовая часть и часть IDoubleDoSomething. Foo :: Base и Foo :: IDoubleDoSomething. Вы можете попробовать это, временно предоставив и реализовав IDoubleDoSomething.DoSomething ().

Но поскольку Foo уже "является базой", у вас есть то, что вам нужно, без IDoubleDoSomething.

...