Создание интерфейса для вектора STL - PullRequest
1 голос
/ 29 декабря 2011

Я смотрю на создание оболочки для вектора контейнера C ++ STL (функциональности будет больше, чем может обеспечить вектор (загрузка, сохранение и т. Д.), Поэтому она должна быть оболочкой).

Потребители моей обертки должны выполнять итерацию, хотя элементы и, если я выставляю итераторы STL, для изменения реализации позже потребуется, чтобы мои вызывающие стороны перекомпилировали (плюс я чувствую, что нарушаю инкапсуляцию).

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

Мои вопросы:

  1. Если я хочу вернуть int для размера вектора, как это работает с size_type? Кажется, что Size_type не должен отображаться в интерфейсе, так как, если он изменяется, вызывающему необходимо будет перекомпилировать. Я был бы рад установить какое-то ограничение, если выяснится, что size_type может быть больше целого (я не думаю, что у меня когда-либо будет столько элементов!)
  2. Циклирует вектор с использованием оператора [] значительно хуже, чем с использованием итераторов

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

Ответы [ 4 ]

2 голосов
/ 29 декабря 2011

Вы можете определить сингелтонский класс ifc (чисто виртуальный), на который будут ссылаться ваши клиенты. Я думаю, что эта модель дизайна называется "подход фабрики Singelton". Надеюсь, мой длинный ответ поможет вам:).

Пока вы не меняете общедоступный интерфейс (списки методов), вашим клиентам не нужно будет перекомпилировать, если вы измените свой код.

что-то вроде:

myClassIfc.h:

Class myClassIfc
{
public:
    virtual ~myClassIfc();    

    ///// list all your pure virtual public ifc methods here ////
    void m_zRunMyMethod(int nNumber) = 0;
    int m_nSize() = 0;

    static myClassIfc* ms_pGetImplObj();      

protected:        
    myClassIfc();
    static myClassIfc* ms_pImplObj;
}

inline myClassIfc* myClassIfc::ms_pGetImplObj()
{
    return ms_pImplObj;
}

myClassIfc.cpp:

#include myClassIfc.h

myClassIfc::myClassIfc()
{
}
myClassIfc::~myClassIfc()
{
}

myClass.h - реализуйте свой чистый виртуальный класс

Class myClass: public myClassIfc
{
public:
    virtual ~myClass();    

    void m_zRunMyMethod(int nNumber);
    int m_nSize();

    static void ms_zCreate();
    static void ms_zDestroy();

protected:        
    myClass();

private:
    vector<int> myInternalVector;
}

myClass.cpp:

#include myClass.h

void myClass::m_zRunMyMethod(int nNumber)
{
   /// your action
   printf("%d\n", nNumber);
}

int myClass::m_nSize()
{
   return int(myInternalVector.size());
}

void myClass::ms_zCreate()
{
    if (NULL != ms_pImplObj)
    {
        return;
    }
    ms_pImplObj = (myClass*) new myClass();   
}

void myClass::ms_zDestroy()
{
    if (NULL == ms_pImplObj)
    {
        return;
    }
    delete ms_pImplObj;
    ms_pImplObj = NULL;
}

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

#include myClassIfc.h


void main(void)
{
    myClassIfc::ms_pGetImplObj()->m_zRunMyMethod(5);
    myClassIfc::ms_pGetImplObj()->m_nSize();         
}

единственное, что я не перечислил выше, это управление вашей памятью, что означает, кто сам создает объект singleton (вызывает статический API ms_zCreate () производного класса). Вы можете позвонить из другого места или напрямую из своего кода.

Обратите внимание, что вы можете манипулировать вышеописанным подходом ifc, чтобы он не был одноэлементным. Пока класс ifc не меняется, клиентскому коду не потребуется повторная компиляция, если вы измените класс наследования (реализации).

1 голос
/ 29 декабря 2011

Многое из того, что вы спрашиваете, идет вразрез с современным дизайном C ++. Я отсылаю вас к книге Херба Штуттера и Андрея Александреску Стандарты кодирования C ++ . Изучите названия этих глав по дизайну классов:

Class Design and Inheritance
32. Be clear what kind of class you’re writing. 56
33. Prefer minimal classes to monolithic classes. 57
34. Prefer composition to inheritance. 58
35. Avoid inheriting from classes that were not designed to be base classes. 60
38. Practice safe overriding. 66
39. Consider making virtual functions nonpublic, and public functions nonvirtual. 68
41. Make data members private, except in behaviorless aggregates (C-style structs). 72
42. Don’t give away your internals. 74
44. Prefer writing nonmember nonfriend functions. 79

Обратите особое внимание на # 44. Вместо переопределения вектора STL создайте функции, не являющиеся членами, которые работают на итераторах STL. Это также связано с # 35. Классы STL не предназначены для базовых классов. # 33 - причина, по которой я сжимаю ваш комментарий, что вы хотите добавить функции «Загрузить» и «сохранить» в вектор STL. Эти виды функций звучат так, как будто они не являются членами.

О, и ты действительно думал об этом:

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

С одной стороны, вы хотите использовать композицию, чтобы скрыть вектор внутри другого класса. Хорошо. Не самый гибкий дизайн, но, возможно, вам нужно взаимодействовать с не-C ++ кодом. Но затем вы хотите представить элемент данных вашего класса как часть интерфейса. Это может создать проблемы, когда ваши клиенты должны будут перекомпилировать. Это не имеет смысла. Либо вы хотите полную инкапсуляцию, либо вам не нужны частые перекомпиляции. Что он? (И, пожалуйста, не моделируйте ничего в классе MFC CArray. Это ужасно.)

1 голос
/ 29 декабря 2011

Я не предлагаю использовать индекс и оператор [] на самом деле.Вместо непосредственного представления векторных итераторов, создайте в вашем классе итераторы typedefs, которые изначально приравниваются к векторным итераторам, но вы можете изменить их на что угодно, не требуя изменений кода от ваших клиентов.Затем вы можете использовать обычные методы begin / end / find.

0 голосов
/ 29 декабря 2011

Вы говорите: «Я хотел бы создать интерфейс, который предоставляет только примитивные типы, чтобы гарантировать, что клиентам не нужно будет перекомпилировать».Как раз наоборотесли вы хотите избежать перекомпиляции, вам нужно сделать все, чтобы пользователь определял тип, используя идиому брандмауэра компиляции.В случае с итераторами это может привести к неприемлемому снижению производительности: идиома итератора требует глубокого копирования, и тот факт, что ничего нельзя встроить, вероятно, также повлияет на оптимизацию.

Я думаю, что полное избегание перекомпиляции, вероятно,не разумно, учитывая, как работает C ++.Более важно то, что вы хотите избежать внесения изменений в клиентский код, если вы измените реализацию.В этом случае, например, вы хотите точно определить, что вы хотите гарантировать;если вы решите, что итераторы в вашем классе должны быть итераторами с произвольным доступом, тогда, вероятно, достаточно typedef до std::vector<>::iterator;если все, что вы хотите гарантировать, это прямая итерация (чтобы предоставить большую свободу в реализации позже), вы можете рассмотреть возможность включения std::vector<>::iterator в свой собственный итератор, который предоставляет только те операции, для которых вы готовы гарантировать поддержку в будущем.версии.

Обратите внимание, что если позднее вы решите использовать реализацию, которая не поддерживает итераторы произвольного доступа, вы также не сможете поддерживать [].Поддержка [] налагает больше ограничений на будущие реализации, чем поддержка только прямых итераторов.

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