Vector, Size_type и Encapsulation - PullRequest
       43

Vector, Size_type и Encapsulation

4 голосов
/ 08 февраля 2010

У меня есть класс с личным членом данных типа vector< A*>.

В классе есть два открытых метода, которые на самом деле используют vector<A*>::size_type:

  1. Метод, возвращающий количество элементов в векторе
  2. Метод, возвращающий элемент в векторе по индексу

Я могу добавить в публичный раздел класса следующий typedef:

typedef vector :: size_type SIZE_t;

но ИМХО это раскрывает слишком много деталей о реализации класса.

Другой подход заключается в использовании size_t.

Что вы думаете?

Ответы [ 6 ]

6 голосов
/ 08 февраля 2010

Я бы использовал typedef в классе. Причина в том, что для std::vector тип размера равен std::size_t, но если позднее вы измените код для использования контейнера (свернутого вручную), тип размера которого не равен std::size_t, переопределения typedef будет достаточно.

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

for ( mytype::size_type i = 0; i < myelement.size(); ++i )

В приведенном выше цикле for пользовательский код не знает, является ли size_type типом со знаком или без знака, он просто работает. Вы можете изменить свою реализацию, и до тех пор, пока вы обновляете typedef, предыдущий код будет компилироваться без предупреждений сравнения со знаком / без знака. Typedef на самом деле помогает инкапсуляции.

4 голосов
/ 08 февраля 2010

Использовать обычный старый size_t для обеих функций-членов.

2 голосов
/ 08 февраля 2010

Какие детали это будут? Единственное, что выставляет size_type - это размер, необходимый для индексации (который почти наверняка будет size_t). Добавление typedef больше не раскрывает информацию.

1 голос
/ 08 февраля 2010

Если вы хотите иметь максимальный уровень инкапсуляции, я бы использовал:

private:
    typedef std::vector<A*> container_type;
    container_type _container;
public:
    typedef container_type::const_iterator const_iterator;

    const_iterator begin()const{ return _container.begin(); }
    const_iterator end()const{ return _container.end(); }

Используя итераторы вместо типа size, вы сможете переключаться между std :: vector и std :: list. Тем не менее, если для вашего класса требуется произвольный доступ, то я бы сказал:

private:
    typedef std::vector<A*> container_type;
    container_type _container;
public:
    typedef container_type::size_type size_type;
    A* operator[](size_type idx)const{ return _container[idx]; }
    size_type size()const{ return _container.size(); }

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

1 голос
/ 08 февраля 2010

Все типы size_t по сути являются одним и тем же скалярным типом, и, поскольку он является скалярным, он неявно конвертируем для компилятора. Таким образом, нет никакой разницы во времени компиляции или во время выполнения между использованием std::size_t, std::vector::size_type или любого другого подобного типа.

Хорошей идеей (и в соответствии с соглашениями) является предоставление typedef для типа размера в вашем классе. IMO typedef, который вы показываете, не раскрывает слишком много вашей реализации, так как клиенты должны использовать ваш typedef, а не vector::size_type напрямую. Но если вы предпочитаете

typedef std::size_t SIZE_T;

это выглядит одинаково хорошо для меня.

0 голосов
/ 08 февраля 2010

Если ваш класс уже использует std::vector<A*> в своей реализации, то добавление typedef std::vector<A*>::size_type size_type не раскрывает больше деталей, чем это уже есть.

Однако, если вы собираетесь использовать полную инкапсуляцию, вам понадобится методика, такая как PIMPL idom или класс интерфейса (также известный как класс протокола), полностью скрывающий, что std::vector<A*> вообще используется в реализации:

До:

#include <vector>
class A;
class Foo {
public:
    typedef std::vector<A*>::size_type size_type;
    size_type get_number_of_stuff() const;
private:
    std::vector<A*> _stuff;
};

После (с использованием техники PIMPL):

class FooImpl;
class Foo {
public:
    typedef size_t size_type; // or just use size_t directly below
    size_type get_number_of_stuff() const;
private:
    FooImpl* _impl;
};

FooImpl определяется в вашем исходном файле, а не в заголовке, полностью скрывая выбор вектора в деталях реализации. Поэтому вам больше не нужно #include <vector> в заголовочном файле, что имеет пару преимуществ:

  • Пользователи вашего заголовочного файла не обязаны (косвенно) включать вектор, если они его не используют. Это может улучшить производительность во время компиляции, что важно для больших баз кода.
  • Если вы измените реализацию (например, в список), вы не рискуете нарушить любой код, который (ошибочно) полагался на ваш #include <vector>, который теперь #include <list>. Это случается.
...