Подтвердить что ты не робот

Какой лучший способ заставить пользователя функции С++ признать семантический смысл параметров, которые являются числовыми константами?

Я хотел бы написать функциональные интерфейсы, которые заставляют пользователя признать семантический смысл встроенных констант. Например, я хотел бы взять

void rotate(float angle); // Rotate the world by an angle in radians.

и измените его на

void rotate(Radians angle);

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

4b9b3361

Ответ 1

Нет, можно сделать класс Radians, который должен быть оптимизирован большинством достойных компиляторов во что-то, что не медленнее обычного float. Вас может заинтересовать boost.units.

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

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

Возможность заставить компилятор принудительно использовать такие ограничения для вас без штрафа за выполнение и, возможно, даже написать весь код конверсии для вас (при очень маленьком штрафе за выполнение) - это одна из очень опрятных вещей о С++ и делает ее полезной добавленная сложность над C.

Вот действительно простая версия того, как этот класс может выглядеть, если вы его закодировали:

#include <cmath>

// This class is tiny enough because it has no virtual functions and only one
// data member that it likely more efficient to pass by value than by
// reference.
class Radians {
 public:
   // If you don't put in explicit, the compiler will automatically convert a
   // float to a Radians for you and undo all of the hard work you did to make
   // sure callers express their intentions.
   explicit Radians(float radians) : value_(radians) {}

   float getValue() const { return value_; }
   void setValue(float radians)  { value_ = radians; }

   float asDegrees() const { return value_ * 180 / M_PI; }

   // This allows you to say Radians r = Radians::fromDegrees(something);
   static Radians fromDegrees(float degrees) {
      return Radians(degrees * M_PI / 180);
   }

 private:
   float value_;
};

Обратите внимание, как все функции объявлены в классе. Это делает их неявно имеющими ключевое слово inline. Хороший компилятор оптимизирует все эти функции из существования. Конечно, функции преобразования будут генерировать код для преобразования, но в остальном он будет таким же, как и с плавающей точкой.

Ответ 2

Ключом к этому ограничению является объявление явного конструктора:

class Radians
{
public:
  explicit Radians(float value) : m_value(value) {}
private:
  float m_value;
};

Таким образом, ваш пользователь не может ввести rotate(4.5). Им нужно будет ввести rotate(Radians(4.5)).

Ответ 3

Это идеальный способ сделать это. Вот что делает C в С++. Используя классы, в которых они вам нужны, и инкапсулируйте данные. Это не делает программу заметной медленнее, поскольку компилятор делает все оптимизации.

Ответ 4

Это не должно быть медленнее.

В библиотеке Ogre3d есть класс Radian. Сгенерированная сборка точно такая же, как и с использованием float напрямую (по крайней мере, когда я последний раз тестировал gcc с включенной оптимизацией)

Ответ 5

Абсолютно возможно написать OOP-код, который так же эффективен, как и процедурный код. Более того, иногда вы можете получить удивительные ускорения, если вы воспользуетесь добавленной семантикой или кодом класса. Одним из замечательных примеров такого преимущества является std::swap. Да, используйте шаблоны;).

Что позволяет написать эффективный класс Радиана, это вложение. Однако используйте его с умом!

Контрольный список:

  • читайте об эффективном встраивании
  • позаботьтесь о конструкторе explicit
  • соблюдайте правильное использование ключевого слова const, где это применимо
  • читайте о Оптимизация С++
  • наконец, если вы делаете много выражений, связанных с вашим классом, google около шаблонов выражений

Ответ 6

С простейшими версиями (typedef, классом с одним атрибутом элемента и встроенными аксессуарами) компилятор может сделать это так же быстро, как простая версия float.

Также рассмотрите возможность использования некоторой библиотеки метапрограммирования, например boost.units.

Ответ 7

Медленнее находится в глазах смотрящего. Как часто вы называете этот код? Да, вы будете создавать большие элементы в стеке при передаче класса Radians, чем примитивный float.

Почему это важно? В какой-то момент пользователь вашего класса должен иметь половину мозга и понимать API. Вы можете объявить его как

void rotate(float radians);

В качестве альтернативы, предполагая, что вы фактически не изменяете угол, вы можете передать свой класс Radians по ссылке const:

void rotate(const Radians &angle);

который предположительно будет быстрее, чем передача по значению.