LINUX.ORG.RU

Работа с множеством интерфейсов

 


0

2

Хочу я сделать такую штуку. Объясню на примере.

Есть, допустим, несколько физических движков, которые поддерживают разные наборы фич. Какие-то, допустим, умеют прикладывать силы, какие-то вращать объекты, какие-то трение. Хочется сделать так, чтобы движки можно было менять хоть налету, но при этом проверка совместимости по фичам была на этапе компиляции.

Как я это вижу:

Есть множество интерфейсов:

IApplyForce { virtual void ApplyForce(int nobj, vector force) = 0; }
IRotateObject { virtual void RotateObject(int nobj, float angle) = 0; }
ISetFriction { virtual void SetFriction(float amount) = 0; }

Движки реализуют его подмножества:

Engine1: public IApplyForce, public IRotateObject, public ISetFriction {}
Engine2: public IApplyForce, public IRotateObject {}
Engine3: public IApplyForce, public ISetFriction {}

Нужна возможность сделать что-то типа указателя на несколько интерфейсов сразу, к которому можно кастануть только классы, реализующие все эти интерфейсы.

// Допустим, пользователю не нужно трение вообще
SmartEnginePointer<IApplyForce, IRotateObject> engine = new Engine1;

// Так можно сделать, Engine2 реализует нужные интерфейсы
engine = new Engine2;

// Тут ошибка компиляции, ибо IRotateObject не реализован
engine = new Engine3;

Есть идеи как это реализовать?

★★★★★

это точно согласуется с принципом подстановки Лисков?

stevejobs ★★★★☆
()
Ответ на: комментарий от slovazap

Красиво не получится, но можно попробовать так:

struct I1 { void f1(); };

struct I2 { void f2(); };

struct I3 { void f3(); };

struct E12 : public I1, I2 {};

struct E23 : public I2, I3 {};

template<class T1, class T2> struct Foo {
        template <class T> void set(T *p)
        {
                p1 = p;
                p2 = p;
        }
        I1 *p1;
        I2 *p2;
};

void f()
{
        Foo<I1, I2> p;
        p.set(new E12);
        p.p1->f1();
        p.p2->f2();
        p.set(new E23); // ошибка
}
tailgunner ★★★★★
()
#include <cstdio>

struct IApplyForce {
    virtual void ApplyForce() = 0;
};

struct IRotateObject {
    virtual void RotateObject() = 0;
};

struct ISetFriction {
    virtual void SetFriction() = 0;
};

template <class...>
struct IComb;

template <>
struct IComb<> {};

template <class T, class... Ts>
struct IComb<T, Ts...> : public T, public IComb<Ts...> {};

struct Engine1: public IComb<ISetFriction, IApplyForce, IRotateObject> {
    virtual void ApplyForce() { puts("Engine1::ApplyForce"); }
    virtual void RotateObject() { puts("Engine1::RotateObject"); }
    virtual void SetFriction() { puts("Engine1::SetFriction"); }
};

struct Engine2: public IComb<IApplyForce, IRotateObject> {
    virtual void ApplyForce() { puts("Engine2::ApplyForce"); }
    virtual void RotateObject() { puts("Engine2::RotateObject"); }
};

struct Engine3: public IComb<IApplyForce, ISetFriction> {
    virtual void ApplyForce() { puts("Engine3::ApplyForce"); }
    virtual void SetFriction() { puts("Engine3::SetFriction"); }
};

int main()
{
    IComb<IApplyForce, IRotateObject> *engine = new Engine1;
    engine->ApplyForce();
    engine->RotateObject();

    engine = new Engine2;
    engine->ApplyForce();
    engine->RotateObject();

    // engine = new Engine3;
}

это не полное решение, потому как с его помощью, например, для

struct ABCD: public IComb<A, B, C, D>;

генерируются следующие LSP-совместимые предки:

ABCD
IComb<A, B, C, D>
A
IComb<B, C, D>
B
IComb<C, D>
C
IComb<D>
D
IComb<>

а нам нужны все размещения. Можно ли с помощью template metaprogramming на variadic templates это сделать — не знаю. Александреску говорил что можно, например, делать slicы на parameter pack-ах, сортировать их и т.п., так что может быть и можно.

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 1)
Ответ на: комментарий от quasimoto

Или просто руками расписывать все нужные размещения:

#include <cstdio>

struct IApplyForce {
    virtual void ApplyForce() = 0;
};

struct IRotateObject {
    virtual void RotateObject() = 0;
};

struct ISetFriction {
    virtual void SetFriction() = 0;
};

template <class...>
struct IComb;

template <>
struct IComb<> {};

template <>
struct IComb<IApplyForce, ISetFriction> : public IApplyForce, public ISetFriction {};

template <>
struct IComb<IApplyForce, IRotateObject> : public IApplyForce, public IRotateObject {};

template <>
struct IComb<IApplyForce, ISetFriction, IRotateObject> : public ISetFriction, public IComb<IApplyForce, IRotateObject> {};

/* ... */

struct Engine1: public IComb<IApplyForce, ISetFriction, IRotateObject> {
    virtual void ApplyForce() { puts("Engine1::ApplyForce"); }
    virtual void RotateObject() { puts("Engine1::RotateObject"); }
    virtual void SetFriction() { puts("Engine1::SetFriction"); }
};

struct Engine2: public IComb<IApplyForce, IRotateObject> {
    virtual void ApplyForce() { puts("Engine2::ApplyForce"); }
    virtual void RotateObject() { puts("Engine2::RotateObject"); }
};

struct Engine3: public IComb<IApplyForce, ISetFriction> {
    virtual void ApplyForce() { puts("Engine3::ApplyForce"); }
    virtual void SetFriction() { puts("Engine3::SetFriction"); }
};

int main()
{
    IComb<IApplyForce, IRotateObject> *engine = new Engine1;
    engine->ApplyForce();
    engine->RotateObject();

    engine = new Engine2;
    engine->ApplyForce();
    engine->RotateObject();

    // engine = new Engine3;
}
quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 2)
Ответ на: комментарий от quasimoto

Во, спасибо - как раз что-то такое представлялось в голове, но я не сильно был знаком с variadic templates. Но да, решение неполное.

slovazap ★★★★★
() автор топика

Паттерн декоратор может? Типа конструируешь объект с нужным количеством фич, потом что-то типа engine->set(PARAMETER_FRICTION, 1.0f). По всем вложенным декораторам бежишь рекурсивно и обрабатываешь функцию. Надеюсь я понятно изложил. Это без использования template.

pozitiffcat ★★★
()
Последнее исправление: pozitiffcat (всего исправлений: 1)

Во-первых, в C++ нет интерфейсов Во-вторых, у тебя наследование не по делу используется(см. LSP - принцип подстановки Барбары Лисков).

forCe
()
Ответ на: комментарий от forCe

Во-первых, роль интерфейсов с возможностью dynamic dispatch в C++ играют pure abstract classes. Во-вторых, всё там в порядке с наследованием и LSP — интерфейсы на то и интерфейсы, чтобы от них множественно наследовать реализации, в том числе с собиранием интерфейсов в их комбинации тем же множественным наследованием, равно как и с подмешиванием к таким комбинациям дополнительных чистых виртуальных методов (в результате остаётся тот же pure abstract class = no data + pure virtual methods only, то есть «интерфейс»).

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 1)
25 августа 2013 г.
Ответ на: комментарий от quasimoto

Разница между интерфейсами и абстрактными классами в том, что интерфейс задает лишь интерфейс и ничего не говорит о наследовании. В С++ же наследование от абстрактного класса - точно такое же наследование, как и от обычного. Ты, например, не сможешь предоставить реализацию «интерфейса», унаследовав ее от другого класса.

Что касается нарушение LSP, то может быть я не до конца пример посмотрел - сейчас не помню уже :)

forCe
()
Ответ на: комментарий от forCe

http://en.wikipedia.org/wiki/Subtyping

In a number of object-oriented languages, subtyping is called interface inheritance.

Реализация — подтип своего интерфейса (SomeImpl <: SomeIface), в языке это обычно выглядит как наследование (обычное как в C++, C# и Scala или «особое» как в Java) классом реализации «класса» интерфейса (он может называться как-то иначе — abstract class, trait, interface).

В С++ же наследование от абстрактного класса - точно такое же наследование, как и от обычного

Как и в Scala с C#. В них интерфейсы определяются как abstract class/trait и interface — наследование от них точно такое же как и от обычного class, с гарантиями того что методы интерфейсов будут воплощены в реализации (в C++ тоже есть эти гарантии). Говорить что в Scala или C# нет интерфейсов это как-то... в общем, не получается :)

Ты, например, не сможешь предоставить реализацию «интерфейса», унаследовав ее от другого класса.

Не понял. На место Parent в (..., Parent&, ...) законно передавать любой Child& который наследник — Child : ..., Parent, ...

quasimoto ★★★★
()

https://gist.github.com/alexeiz/019f31a948df86542559

Работает так:

struct Interface1 { virtual ~Interface1() {} };
struct Interface2 { virtual ~Interface2() {} };
struct Interface3 { virtual ~Interface3() {} };

struct Concrete1 : Interface1, Interface2, Interface3 {};
struct Concrete2 : Interface1, Interface2 {};
struct Concrete3 : Interface1, Interface3 {};

int main()
{
    interface_ptr<Interface1, Interface2> ip1 = new Concrete2();

    // error: Concrete3 doesn't implement Interface2
    // interface_ptr<Interface1, Interface2> ip2 = new Concrete3();

    Interface2 * if2 = get_iface<Interface2>(ip1);

    // error: ip1 doesn't contain a pointer implementing Interface3
    // Interface3 * if3 = get_iface<Interface3>(ip1);
}

Идея в том, что interface_ptr определяет enabled_if конструктор, если concrete класс наследуется ото всех интерфейсов (is_derived_from). Иначе присваивание невозможно. Тоже самое и с get_iface, только здесь интерфейсный параметр должен быть одним из интерфейс классов, которые использовались при инстанциировании inteface_ptr (is_one_of).

alexeiz
()
Ответ на: комментарий от quasimoto

Не понял.

Например: http://ideone.com/WFzF0g

using System;
 
interface IA
{
    void foo();
}
 
class B // нет реализации IA
{
    public void foo() // метод не виртуальный
    {
        Console.WriteLine("B");
    }
}
 
class C : B, IA
{
}
 
 
public class Test
{
    public static void Main()
    {
        IA a = new C;
        a.foo();
    }
}

forCe
()
Ответ на: комментарий от forCe

Ну C# это не показатель — если в нём есть «заочное» наследование интерфейса, то есть по факту B : IA, это не значит, что там где его нет нет и самих интерфейсов.

1. Это может быть полезно если B это не наш класс и мы хотим придумать свой интерфейс и заочно считать что B : IA, тогда:

#include <cstdio>
struct B { void foo() { puts("B"); } };
struct IA { virtual void foo() = 0; virtual ~IA() {} };
// struct BImplIA : B, IA { void foo() { B::foo(); } };
struct C : B, IA { void foo() { B::foo(); } };
// struct C : BImplIA {};
int main() { auto x = C(); IA &a = x; a.foo(); }

2. Иначе будет просто правильный вариант:

#include <cstdio>
struct IA { virtual void foo() = 0; virtual ~IA() {} };
struct B : IA { void foo() { puts("B"); } };
struct C : B {};
int main() { auto x = C(); IA &a = x; a.foo(); }

3. А вот в C# такой код будет весело наворачиваться в рантайме:

using System;
interface IA { void foo(); }
class B { static public void foo() { Console.WriteLine("B"); } }
class C : B, IA {}
public class Test { public static void Main() { var x = new C(); IA a = x; a.foo(); } }

тогда как в C++ компилятор не пропустит. Так что

using System;
interface IA { void foo(); }
class B { static public void foo() { Console.WriteLine("B"); }}
class C : B, IA { public void foo() { B.foo(); }}
public class Test { public static void Main() { var x = new C(); IA a = x; a.foo(); }}

аналогично void foo() { B::foo(); }. Или можно проще?

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

А вот в C# такой код будет весело наворачиваться в рантайме

Такой код, очевидно, должен не проходить компиляцию.

msdn

To implement an interface member, the corresponding member of the implementing class must be public, non-static, and have the same name and signature as the interface member.

И он не проходит ее в ms-компиляторе. Можно смело писать багрепорт в mono.

forCe
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.