Использование шаблона для раскрытия частного определения типа - PullRequest
4 голосов
/ 31 марта 2011

У меня есть класс, который содержит закрытый typedef и несколько членов Функции:

class Foo
{
private:
  typedef std::blahblah FooPart;
  FooPart m_fooPart;
  ...
public:
  int someFn1();
  int someFn2();
};

Несколько функций-членов должны использовать m_fooPart аналогичным образом, поэтому я хочу поместить это в функцию. Я ставлю вспомогательные функции в анонимном пространство имен всякий раз, когда я могу, но в этом случае они должны знать, что FooPart есть. Итак, я сделал это:

namespace
{
  template <typename T>
  int helperFn(const T& foopart, int index)
  {
    ...
    return foopart.fn(index);
  }
}

int Foo::someFn1()
{
  ...
  return helperFn(m_fooPart, ix);       
}

Вынуждая компилятор генерировать тип FooPart, я все еще в земля четко определенного поведения? Есть ли более элегантный способ выполнение этого, что не увеличивает размер Foo или обнародовать что сейчас личное?

Ответы [ 3 ]

4 голосов
/ 31 марта 2011

Да, такой подход дает четко определенное, совместимое со стандартами поведение.

Тем не менее, добавление функций-членов в класс не увеличивает размер класса (при условии, что вы имеете в виду результат оператора sizeof), поэтому я не уверен, какой недостаток вы ощущаете, просто делая вспомогательную функцию приватный член Foo.

3 голосов
/ 31 марта 2011

Простой ответ: сделайте typedef общедоступным.

Это приведет к утечке незначительных деталей реализации (фактический внутренний тип), но поскольку он определен по типу, вы можете переопределить его в любое время, и все должно быть в порядке.

Немного менее просто: воспользуйтесь вспомогательной функцией, обеспечивающей доступ к вашему внутреннему типу.

Проблема с этим вторым подходом заключается в том, что вы предоставляете не только доступ к typedef, но и ко всем закрытым частям вашего класса, и это может быть не самой лучшей идеей. В любом случае, поскольку это внутренняя вспомогательная функция, она находится под вашим собственным контролем, и с ней все должно быть в порядке. (Теперь, когда я думаю об этом, вы можете объявить функцию в именованном пространстве имен, чтобы объявление friend было успешным)

Еще менее просто: создайте отдельную typedef внутри файла реализации и убедитесь, что они синхронизированы.

Вы можете убедиться, что типы совпадают с небольшим битом метапрограммирования, с шаблоном same_type<T,U>, который предоставит значение true, если оба типа одинаковы, и false в противном случае. Статическое утверждение вызовет ошибку, если typedef изменяется только в одном месте

Вернемся к простому: предоставьте typedef или используйте тип напрямую без статического утверждения.

Вы вызываете функцию (это не должен быть шаблон, как в вашем коде) и передаете ссылку. Если typedef изменится в классе, вызов не удастся, и компилятор скажет вам.

Я бы выбрал последний вариант, хотя он может показаться немного грубым и менее деликатным , чем другие, но дело в том, что это только деталь реализации, которая не используется другими, вы под полным контролем кода и хорошо, просто лучше.

РЕДАКТИРОВАТЬ , после комментария.

Я начал писать это как комментарий, но он стал слишком длинным, поэтому я добавляю его в ответ.

В этом решении нет ничего плохого, кроме того, что вы без необходимости делаете функцию универсальной, а некоторые сообщения об ошибках в будущем могут быть не такими простыми, как с неуниверсальной сигнатурой. Обратите внимание, что template будет не выставлять typedef (как предполагает заголовок вопроса), а скорее сделает компилятор вывод типом в месте вызова.

Если вы измените typedef, вместо того, чтобы получить сообщение о том, что аргументы helperFn не могут быть сопоставлены с существующей функцией, тип будет выведен, а функция сопоставлена, но вы получите ошибку глубже helperFn если вы используете свойство того типа, которого больше нет. Или, что еще хуже, вы можете даже не получить ошибку, если изменилась семантика типа.

Учтите, что typedef имеет значение std::list<X> и что в функции, которую вы перебираете, с этим простым корректным циклом for:

for (typename T::iterator it=x.begin(), end=x.end(); it != end; ) {
   if ( condition(*it) ) 
      it = x.erase(it); 
   else 
      ++it; 
}

Можете ли вы уловить эффект изменения typedef на std::vector<X>? Компилятор не может, даже если код сейчас неверен. Является ли написание цикла for подобной идеей, или почему это не просто использование erase-remove идиома - это разные проблемы (на самом деле предыдущий цикл возможно лучше чем стереть-удалить для списка), конкретная ситуация заключается в том, что семантика изменилась, и поскольку тип синтаксически совместим с предыдущим, компилятор не заметит, что код неправильный, он не укажет вам на эту функцию, и есть вероятность, что вы не будете его пересматривать / переписывать.

1 голос
/ 31 марта 2011

Я предполагаю, что это идея общего программирования - делать вещи с частью Foo, не зная ее типа. Более «традиционным» (строго типизированным, скучным, читаемым, дублирующим код - вы называете это) способом было бы явное упоминание типа:

int helperFn(const std::blahblah& foopart, int index)
{
    ...
    return foopart.fn(index);
}
...