Что я не получаю об этой реализации абстрактного класса? - PullRequest
1 голос
/ 18 мая 2010

ПРЕДИСЛОВИЕ: я относительно неопытен в C ++, так что это вполне может быть вопрос первого дня.

Я работаю над тем, чья долгосрочная цель заключается в переносимости между несколькими операционными системами. У меня есть следующие файлы:

Utilities.h

#include <string>

class Utilities
{
public:
    Utilities() { };
    virtual ~Utilities() { };

    virtual std::string ParseString(std::string const& RawString) = 0;
};

UtilitiesWin.h (для класса / реализации Windows)

#include <string>
#include "Utilities.h"

class UtilitiesWin : public Utilities
{
public:
    UtilitiesWin() { };
    virtual ~UtilitiesWin() { };

    virtual std::string ParseString(std::string const& RawString);
};

UtilitiesWin.cpp

#include <string>
#include "UtilitiesWin.h"

std::string UtilitiesWin::ParseString(std::string const& RawString)
{
    // Magic happens here!
    // I'll put in a line of code to make it seem valid
    return "";
}

Так что в другом месте моего кода у меня есть это

#include <string>
#include "Utilities.h"

void SomeProgram::SomeMethod()
{
    Utilities *u = new Utilities();
    StringData = u->ParseString(StringData); // StringData defined elsewhere
}

Компилятор (Visual Studio 2008) умирает от объявления экземпляра

c:\somepath\somecode.cpp(3) : error C2259: 'Utilities' : cannot instantiate abstract class
        due to following members:
        'std::string Utilities::ParseString(const std::string &)' : is abstract
        c:\somepath\utilities.h(9) : see declaration of 'Utilities::ParseString'

Поэтому в данном случае я хочу использовать абстрактный класс (Utilities) как интерфейс и попросить его перейти к реализованной версии (UtilitiesWin).

Очевидно, что я делаю что-то не так, но я не уверен, что. Когда я пишу это, мне приходит в голову, что между реализацией UtilitiesWin абстрактного класса Utilities, которую я пропустил, вероятно, есть важная связь, но я не уверен, где. Я имею в виду следующие работы

#include <string>
#include "UtilitiesWin.h"

void SomeProgram::SomeMethod()
{
    Utilities *u = new UtilitiesWin();
    StringData = u->ParseString(StringData); // StringData defined elsewhere
}

но это означает, что мне придется условно просмотреть различные версии позже (то есть UtilitiesMac(), UtilitiesLinux() и т. Д.)

Что я здесь пропустил?

Ответы [ 4 ]

7 голосов
/ 18 мая 2010
Utilities *u = new Utilities();

говорит компилятору создать новый экземпляр класса Utilities; тот факт, что UtilitiesWin расширяет его, не обязательно известен и не влияет на него. Может быть много классов, расширяющих Utilities, но вы сказали компилятору создать новый экземпляр Utilities, а не эти подклассы.

Звучит так, как будто вы хотите использовать фабричный шаблон, который должен создать статический метод в Utilities, который возвращает Utilities*, который указывает на конкретный экземпляр:

static Utilities* Utilities::make(void) {return new UtilitiesWin();}

В какой-то момент вам придётся создать экземпляр неабстрактного подкласса; нет способа указать UtilitiesWin в этой точке

2 голосов
/ 18 мая 2010

Вы "понимаете" это правильно, вы должны создать конкретный тип. Есть общие решения для этого.

Да, вы должны принять решение, какой класс создавать где-либо.
Реализация этого зависит от критериев для этого решения: фиксировано ли это для двоичного? Один и тот же выбор для каждого процесса? Или он меняется для каждого экземпляра SomeProgram?

Для конкретных классов, которые вы упомянули, решение может быть принято во время компиляции, подобно тому, что предлагает Том.

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

class SomeProgram
{
   private:
     Utilities * m_utilities;

   public:
     Someprogram(Utilities * util) : m_utilities(util) {}

}

Обратите внимание, что SomeProgram только "знает" абстрактный класс, но ни один из конкретных классов.

Для отложенного строительства используйте фабрику. Если класс утилит должен быть введен, как указано выше, его создание дорого, но в большинстве случаев не требуется, вместо этого вы вводите фабрику: вы передаете UtilityFactory классу, который SomeProgram может использовать для создания требуемого экземпляра по требованию. Реальная фабричная реализация решает конкретный класс для выбора. См. Заводской шаблон для получения дополнительной информации.

Если это общая проблема , посмотрите на Инверсия управления (IoC) - есть несколько реализаций библиотеки, которые облегчают эту задачу. Это стало модным словом после агрессивного модульного тестирования, где замена «реальных» реализаций на макеты должна происходить постоянно. (Я все еще жду полной версии MockOS). Тем не менее, я не работал ни с одним приложением, которое серьезно нуждалось в такой библиотеке на практике, и это очень вероятно для вашей проблемы.

2 голосов
/ 18 мая 2010

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

Utilities *u = new Utilities();

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

2 голосов
/ 18 мая 2010

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

#ifdef windows
 Utilities* u = new UtilitiesWin();
#endif
#ifdef spaceos3
 Utilities* u = new UtilitiesSpaceOS3();
#endif

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

...