Как избежать RTTI в дизайне - PullRequest
       3

Как избежать RTTI в дизайне

1 голос
/ 13 декабря 2010

У меня есть приложение Visual Studio 2008 C ++ с несколькими типами объектов, которые являются производными от общей базы.Например:

class Base
{
public:
    std::string Time() const { /*return formatted time*/; };
private:
    SYSTEMTIME time_;
};

class Foo : public Base
{
public:
    const char* FooFunc() const { return "Hello from foo!"; };
};

typedef std::vector< Base > BaseList;

class Bar : public Base
{
public:
    const char* BarFunc() const { return "Hello from bar!"; };

    void push_back( const Base& obj ) { list_.push_back( obj ); };
    BaseList::const_iterator begin() const { return list_.begin(); };
    BaseList::const_iterator end() const { return list_.end(); };

private:
    BaseList list_;
};

Эти объекты хранятся в std::vector< Base >.Мне нужно вывести информацию в каждом из классов Foo и Bar, а также информацию, хранящуюся в базе. Но я бы хотел избежать RTTI .

int main( int, char** )
{
    BaseList list;

    Foo foo;
    Bar bar;
    Foo foo2;

    list.push_back( foo );
    list.push_back( bar );
    bar.push_back( foo2 );

    for( BaseList::const_iterator it = list.begin();
         it != list.end();
         ++it )
    {
        printf( "%s ", it->Time() );

        // print Foo information for objects of type Foo
        // OR print Bar information for objects of type Bar.
        // Descend in to objects of type Bar to print its children.
    }
    return 0;
}

В этом случае желаемый результат будет:

11:13:05 Hello from foo!
11:22:14 Hello from bar!
    11:26:04 Hello from foo!

Какие изменения я могу внести в этодизайн, который позволил бы избежать использования RTTI в качестве решения, но все же позволил бы мне хранить объекты типа Foo и Bar с различной функциональностью во вложенной древовидной структуре?

Спасибо, PaulH

Ответы [ 5 ]

14 голосов
/ 13 декабря 2010

Это ванильный полиморфизм времени исполнения через наследование.

Замените FooFunc и BarFunc реализациями чисто виртуального метода DoTheOutput, унаследованного от Base, а затем вызовите этот метод через Base* в цикле. Если вам тоже нужен вывод Base, тогда Base::DoTheOutput не будет чистым и будет вызываться реализациями подкласса после того, как они завершили производный вывод, специфичный для класса.

Если вам нужно сохранить BarFunc и FooFunc в интерфейсе без изменений, их можно делегировать новой функции DoTheOutput.

10 голосов
/ 13 декабря 2010

Во-первых, существует проблема с классом Bar.Так как вы используете std::vector< Base >, ваш объект разрезан (то есть копия базовой части объекта вставляется в вектор, а не вы объект).Вы захотите использовать std::vector< Base* >, то есть вектор указателей.

Во-вторых, чтобы делать то, что вы хотите, вы можете использовать виртуальный метод в базовом классе, переопределяемый как классом Foo, так и классом Bar.,Если вы считаете, что вашему приложению потребуется более одной из этих операций обхода, вы можете взглянуть на шаблон Visitor .

6 голосов
/ 13 декабря 2010

Хорошо, по предложению Альфа, я превращаю свой комментарий в ответ.

Даже RTTI здесь не будет работать, так как вы храните Base объекты в векторе. Объекты будут нарезаны , теряя любую информацию из производного класса. Вам нужно хранить Base указатели, желательно умные указатели. Кроме того, у вас нет виртуальных функций. Для работы RTTI требуется как минимум одна виртуальная функция.

Сделайте вашу функцию чисто виртуальным методом в Base, затем переопределите его в каждом производном классе с соответствующим поведением. Это сделает Base абстрактный класс, что сделает проблему среза невозможной.

2 голосов
/ 13 декабря 2010

Похоже, вы хотите использовать полиморфизм.Создайте виртуальную функцию и сделайте так, чтобы ее использовали подклассы.Затем составьте список указателей на базу:

class Base
{
public:
    virtual const char *Func() = 0; // pure virtual, no implementation
};

class Foo: public Base
{
public:
    virtual const char *Func() { return "Hello from foo!"; }
};

class Bar: public Base
{
public:
    virtual const char *Func() { return "Hello from bar!"; }
};

int main()
{
    std::list<Base*> myList;
    myList.push_back(new Foo());
    myList.push_back(new Bar());

    for( std::list<Base*>::const_iterator it = myList.begin();
         it != myList.end();
         ++it )
    {
        printf( "%s ", (*it)->Func() );
    }

// don't forget to delete your pointers.  Or better yet, use smart pointers

    return 0;
}
0 голосов
/ 13 декабря 2010

У меня есть приложение Visual Studio 2008 C ++ с несколькими типами объектов, которые являются производными от общей базы.... Эти объекты хранятся в std :: vector .Мне нужно вывести информацию в каждом из классов Foo и Bar, а также информацию, хранящуюся в базе.Но я бы хотел избежать RTTI.... Какие изменения я могу внести в этот дизайн, чтобы избежать использования RTTI в качестве решения, но все же позволил бы мне хранить объекты, такие как Foo и Bar, с различными функциями в виде вложенной древовидной структуры?

Важное примечание : вы говорите, что используете std::vector<Base>, если это правда, вам нужно изменить значение на std::vector<Base*> (или контейнер указателя Boost), чтобы избежать нарезки объектов.

Сводка : у вас есть базовый класс и производные от него классы.Вы хотите, чтобы классы, производные от базового класса, делали вещи на основе того, чем они являются на самом деле, но также хотите, чтобы базовый класс предоставлял методы, которые имеют смысл для всех классов, производных от него.*: переименуйте FooFunc и BarFunc соответственно.Пусть они переопределят virtual функцию в вашем базовом классе:

class Base {
public:
    std::string Time() const { /*return formatted time*/; };
    // this is a pure virtual function (the "= 0" part)
    // there are other kinds of virtual functions that would also work
    virtual const char* Func() const = 0;
private:
    SYSTEMTIME time_;
};

class Foo : public Base {
public:
    const char* Func() const { return "Hello from foo!"; };
};

typedef std::vector<Base*> BaseList;

class Bar : public Base
{
public:
    const char* Func() const { return "Hello from bar!"; };

    void push_back( const Base& obj ) { list_.push_back( obj ); };
    BaseList::const_iterator begin() const { return list_.begin(); };
    BaseList::const_iterator end() const { return list_.end(); };

private:
    BaseList list_;
};

int main( int, char** )
{
    BaseList list;

    Foo foo;
    Bar bar;
    Foo foo2;

    // you normally don't want to do it this way, but since the
    // container won't outlive the stack objects you'll be safe.
    list.push_back(&foo);
    list.push_back(&bar);
    bar.push_back(&foo2);

    for(BaseList::const_iterator it = list.begin();
        it != list.end();
        ++it )
    {
        // the "extra" * was added because the list is a vector<Base*>
        // instead of a vector<Base>
        printf("%s ", *it->Time());

        // print Foo information for objects of type Foo
        // OR print Bar information for objects of type Bar.
        printf("%s\n", *it->Func());
        // Descend in to objects of type Bar to print its children.

        // This you'll need to use RTTI to know when you're looking at a
        // Bar object and need to descend into it
        bar* = dynamic_cast<Bar*>(*it);
        if (bar == NULL)
            continue;
        for (BaseList::const_iterator bit = bar->begin(); bit != bar->end(); ++bit)
            printf("\t%s\n", bit->Func());
    }
    return 0;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...