Я разрабатываю библиотеку с нуля и хочу как можно лучше получить публичный 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_iterator
aunique_ptr<const A>::const_iterator
, чтобы получить мою любимую константу. Опять неприятные броски или промежуточные объекты. И пользователь не мог легко использовать его на основе диапазонаfor
.
Какое очевидное решение мне не хватает?
Edit:
-
B
должен всегда иметь возможность модифицироватьA
, который удерживается, поэтому объявлениеaObjs_
какvector<std::unique_ptr<const A>>
не является параметром. -
Пусть
B
придерживается концепции итератора для итерации поA
s, не является параметром, так какB
будет содержать списокC
и конкретныйD
(или none).