Введение
Итак, с недавнего времени в С++ появилась возможность квалифицировать функции-члены ссылкой (по крайней мере, внешне это выглядит как ссылка). Эти знаки квалификации могут быть lvalue, rvalue ссылками, могут сочетаться с const квалификацией.
class some_type { void foo() & ; void foo() && ; void foo() const & ; void foo() const && ; };
Зачем это нужно?
Строго говоря, официально это фича называется немного по-другому, а именно “ref-qualifiers for *this” или “rvalue references for *this”. Но мне кажется это название немного сбивает с толку, так как может показаться, что объект меняет тип при вызове функций с различной квалификацией. А на самом деле, тип *this никогда не меняется. Так в чем же фишка? А фишка в том, что благодаря этим квалификаторам становится возможным перегружать функции-члены по контексту (rvalue, lvalue, etc) в котором используется объект.
int main() { some_type t; t.foo(); // some_type::foo() & some_type().foo(); // some_type::foo() && }
Как это работает?
Начнем с того, что в С++ уже давно существует механизм разрешения перегрузки между функциями-членами и свободными функциями. Зачем он нужен спросите вы, ведь и так можно понять вызывается ли свободная функция или метод класса хотя бы внешне, по синтаксису, в одном случае obj.f(), в другом просто f()? Дело в том, что когда дело доходит до перегрузки операторов, различий в синтаксисе уже может и не быть. Например
struct some_type { bool operator == (int); }; bool operator == (const some_type& l, long r); void g() { some_type t; int i = 42; t == i; // Какую функцию вызвать? }
Для разрешения такой перегрузки компилятор представлял функцию-член в виде свободной функции с дополнительным параметром — ссылкой на объект, у которого происходит вызов функции и дальше разрешал перегрузку среди всех свободных функций. Так что для реализации нововведения нужно было всего лишь немного “подкрутить” уже существующее поведение, а именно создавать различные сигнатуры кандидатов перегрузки для различно квалифицированных функций-членов.
Скажу еще пару слов о том, как конкретно работает этот механизм, ибо далеко не всегда очевидно, какая именно функция является лучшим кандидатом для перегрузки в том или ином случае. Рассмотрим еще раз код из первого примера.
class some_type { void foo() & ; // 1 void foo() && ; // 2 void foo() const & ; // 3 void foo() const && ; // 4 }; void g() { some_type().foo(); }
Для этого вызова подходят 3 кандидата: 2, 3 и 4. Для разрешения между ними в стандарте существуют особые правила, которые на бумаге выглядят довольно многословными и сложными, но суть которых сводится к тому, что выбирается функция, наиболее точно соответствующая типу.
Попробую пересказать цепь рассуждений по выводу кандидата, как я ее себе представляю. В данном примере выражение some_type() — rvalue. Потенциально могут быть вызваны функции 2, 3 или 4. Но rvalue reference квалифицированные функции более “соответствуют” типу исходного выражения (rvalue), чем const &. Остаются варианты 2 и 4. В четвертом варианте для полного соответствия нужно сделать дополнительное действие над исходным типом — добавить const, тогда как во 2ом варианте никаких дополнительных действий не требуется. Поэтому в итоге будет выбран вариант 2.
Как использовать?
Использовать это нововведение, очевидно, удобно в тех случаях когда поведение объекта должно различаться от контекстов, в котром он используется. Например, мы можем сделать более безопасным использование указателя на хранимый ресурс при использовании RAII.
class file_wrapper { public: // ... operator FILE* () {return held_;} ~file_wrapper() {fclose(held_);} private: FILE* held_; };
В данном примере operator FILE* () представляет собой огромную дыру в безопасном использовании файловой обертки.
Представьте себе такой контекст использования:
FILE* f = file_wrapper("some_file.txt", "r"); // Работа с f
Теперь у нас появляется возможность сделать эту, в сущности очень удобную, функцию более (но не полностью) безопасной.
operator FILE* () & {return held_;} // Можно вызвать только у lvalue объектов
Можно посмотреть на RAII и с немного другой стороны. Раз мы можем теперь “понять”, что нас вызывают в разных контекстах, давайте просто передавать владение ресурсом вместо копирования в тех случаях, когда дальше использоваться наш объект больше не будет.
template <typename T> class some_type { public: operator std::unique_ptr<T>() const & { return std::unique_ptr<T>(new T(*held_)); // Копируем } operator std::unique_ptr<T>() && { return std::move(held_); // Отдаем владение } private: std::unique_ptr<T> held_; }; some_type f(); void g() { std::unique_ptr<widget> p = f(); }
ссылка на оригинал статьи http://habrahabr.ru/post/216783/
Добавить комментарий