Я разрабатываю библиотеку с нуля и хочу как можно лучше получить публичный API. Я хочу, чтобы компилятор кричал на меня за неправильное использование. Поэтому я наложил на себя следующие правила:
-
истинная (т.е. глубокая и полная) константная корректность во всей библиотеке
Все вещи (локальные переменные, переменные-члены, функции-члены), которые не должны изменяться, объявляются
const. Эта константа должна распространяться на все вложенные элементы и типы. -
Явное и выразительное владение
В соответствии с Основными правилами С++ я определяю это как (iff в математическом смысле if и только if):
- аргументы функции
unique_ptr<T>илиT&&, если функция потребляет ее (т.е. получает право собственности) - аргументы функции
shared_ptr<const T>илиT const&, если функция только читает ее - аргументы функции
shared_ptr<T>илиT&, если функция изменяет его, не принимая права собственности - значения возврата
unique_ptr<T>илиT, если функция передает право собственности вызывающему абоненту - Возвращаемые значения
shared_ptr<const T>илиT const&, если вызывающий объект должен только читать его (хотя вызывающий может построить его копию - данныйTможно скопировать) - никакие функции не должны возвращать
shared_ptr<T>,T&илиT*(так как это позволяло бы неконтролируемые побочные эффекты, которые я стараюсь избегать по дизайну)
- аргументы функции
-
скрытые подробности реализации
В настоящее время я собираюсь с абстрактными интерфейсами с заводами, возвращающими реализацию как
unique_ptr<Interface>. Хотя, я открыт для альтернативных шаблонов, которые решают мою проблему, описанную ниже.
Я не забочусь о виртуальных поисках таблиц и хочу избегать динамических бросков всеми способами (я вижу их как запах кода).
Теперь, учитывая два класса A и B, где B принадлежит переменное число A s. Кроме того, мы выполняем B -implementation BImpl (реализация A, вероятно, не используется здесь):
class A
{};
class B {
public:
virtual ~B() = default;
virtual void addA(std::unique_ptr<A> aObj) = 0;
virtual ??? aObjs() const = 0;
};
class BImpl : public B {
public:
virtual ~BImpl() = default;
void addA(std::unique_ptr<A> aObj) override;
??? aObjs() const override;
private:
std::vector<unique_ptr<A>> aObjs_;
};
Я привязался к возвращаемому значению B getter к вектору A s: aObjs().
Он должен предоставить список A как значения, доступные только для чтения, без передачи права собственности (правило 2.5 выше с константной корректностью) и по-прежнему предоставлять вызывающему абоненту легкий доступ ко всем A s, например. для использования в диапазоне for или стандартных алгоритмах, таких как std::find.
Я предложил следующие опции для ???:
-
std::vector<std::shared_ptr<const A>> const&Мне пришлось бы создавать новый вектор каждый раз, когда я вызываю
aObjs()(я мог бы кэшировать его вBImpl). Это кажется не только неэффективным и ненужным сложным, но и очень субоптимальным. -
Замените
aObjs()парой функций (aObjsBegin()иaObjsEnd()), пересылающих константный итераторBImpl::aObjs_.Подождите. Мне нужно сделать это
unique_ptr<A>::const_iteratoraunique_ptr<const A>::const_iterator, чтобы получить мою любимую константу. Опять неприятные броски или промежуточные объекты. И пользователь не мог легко использовать его на основе диапазонаfor.
Какое очевидное решение мне не хватает?
Edit:
-
Bдолжен всегда иметь возможность модифицироватьA, который удерживается, поэтому объявлениеaObjs_какvector<std::unique_ptr<const A>>не является параметром. -
Пусть
Bпридерживается концепции итератора для итерации поAs, не является параметром, так какBбудет содержать списокCи конкретныйD(или none).