Синтаксические рекомендации по владению и выпуску объектов в C++
Я хочу знать - есть ли какие-либо рекомендации относительно синтаксиса функций-членов C++ (не), которые позволяют мне понять (без комментариев, если возможно) политику владения его аргументами и возвращаемым значением. Под собственностью я подразумеваю, что владелец несет ответственность за уничтожение принадлежащего ему объекта.
Я различаю следующие правила об аргументах:
- взять на себя ответственность
- не бери в собственность
- доля
и о возвращаемом значении:
- релиз ("возврат по значению" находится в этой группе)
- не отпускай
- доля
Например, передача объекта по ссылке не требует его владения:
void func(object & obj) { ... }
Такие рекомендации могут использовать стандартные конструкции, такие как unique_ptr, shared_ptr и т. Д. Если таких рекомендаций нет, то примеры возможных недопониманий синтаксиса также приветствуются.
3 ответа
Я не понимаю, почему использование умных указателей недостаточно. Я не могу думать ни о чем другом, что бы я не отнес к категории запахов кода. Использование умных указателей над необработанными указателями делает владение и ответные действия совершенно ясными:
auto_ptr
/unique_ptr
- единственный владелец, право собственности передаетсяshared_ptr
- несколько владельцев, право собственности может быть переданоscoped_ptr
- один владелец, право собственности не может быть переданоweak_ptr
- наблюдатель (ноshared_ptr
может быть создан изweak_ptr
)
Я думаю, что этого достаточно, чтобы четко показать обязанности, например
void func(std::auto_ptr<Class> input) {...} // func() takes ownership of input
void func(std::shared_ptr<Class> input) {...} // func() and caller share ownership
std::auto_ptr<Class> func() {...} // caller takes ownership of returned value
std::shared_ptr<Class> func() {...} // func() and caller shares ownership of returned object
std::weak_ptr<Class> func() {...} // func() owns created object, but caller may observe it
Как вы упомянули, ссылки также хороши в этом смысле. Обратите внимание, если есть необходимость освободить указатели, используя какой-то собственный механизм, shared_ptr
а также unique_ptr
поддерживает пользовательские удалители. auto_ptr
не имеет этой возможности.
Заметка! Если вы используете C++ до 11, вам придется прибегнуть к boost::shared_ptr
а также boost:weak_ptr
,
Если я вас понимаю, тогда Boost call_traits может быть тем, что вы ищете:
Пример (скопированный из документации идет):
template <class T>
struct contained
{
// define our typedefs first, arrays are stored by value
// so value_type is not the same as result_type:
typedef typename boost::call_traits<T>::param_type param_type;
typedef typename boost::call_traits<T>::reference reference;
typedef typename boost::call_traits<T>::const_reference const_reference;
typedef T value_type;
typedef typename boost::call_traits<T>::value_type result_type;
// stored value:
value_type v_;
// constructors:
contained() {}
contained(param_type p) : v_(p){}
// return byval:
result_type value() { return v_; }
// return by_ref:
reference get() { return v_; }
const_reference const_get()const { return v_; }
// pass value:
void call(param_type p){}
};
Не намного яснее, чем param_type
, reference_type
а также return_type
указать, что имеется в виду.
Я просто использую этот синтаксис для параметров, где это необходимо:
пример объявления конструктора:
t_array(const t_ownership_policy::t_take& policy, THESpecialType* const arg);
Используется на месте вызова:
t_array array(t_ownership_policy::Take, THESpecialTypeCreate(...));
куда t_ownership_policy::t_take
просто фиктивное переименованное имя перегрузки
В этой системе существует несколько политик, каждая из которых имеет отдельные типы. Я предпочел уникальные типы для каждой политики, потому что типизированное перечисление (например) не поддерживает инициализацию так же легко, и слишком легко передать неподдерживаемую политику функции или конструктору. "Полиморфные" политики могут уменьшить количество символов, но это неприятно, потому что это продвигает обнаружение ошибок во время выполнения.
Для "возвращения":
void func(t_container<t_type>& outValue);
куда t_container
какой бы тип контейнера указателя вы не выбрали. Тогда тип контейнера уже реализует необходимый шаблон. Этот контейнер может быть что-то вроде shared_ptr
или специализация, которую вы написали.
и для более сложных типов, я часто буду использовать синтаксис так:
void func(t_special_container& outValue) {
...
outValue.take(ptr);
- or -
outValue.copy(ptr);