Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Ref-qualified member functions

Anna | 24.06.2014 | нет комментариев
В этом посте я расскажу о новой и (как мне кажется) касательно малоизвестной фиче C - reference-qualified member functions. Расскажу о правилах перегрузки таких функций, а также, в качестве примера применения, расскажу, как с поддержкой ref-qualified функций дозволено попытаться усовершенствовать схему управления источниками, реализуемую с поддержкой иной идиомы С — RAII.

Вступление

Выходит, с недавнего времени в С возникла вероятность квалифицировать функции-члены ссылкой (по крайней мере, наружно это выглядит как ссылка). Эти знаки квалификации могут быть lvaluervalue ссылками, могут гармонировать с 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();
}

 

Источник: programmingmaster.ru

 

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB