C ++ и динамически типизированные языки

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

В C ++, как и в других статически типизированных языках, тип переменной указывается во время компиляции. Например, допустим, я должен читать из файла большое количество чисел, которые в большинстве случаев довольно малы, достаточно малы, чтобы вписаться в unsigned short тип. Здесь идет сложная вещь, небольшое количество этих значений намного больше, достаточно больших, чтобы потребоваться unsigned long long для хранения.

Поскольку я предполагаю, что собираюсь выполнять вычисления со всеми из них, я хочу, чтобы все они были сохранены в одном контейнере в последовательных позициях памяти в том же порядке, что и я читал их из входного файла. , Наивный подход заключался бы в том, чтобы хранить их в vector типа unsigned long long , но это означает, что обычно имеет в 4 раза дополнительное пространство того, что на самом деле необходимо ( unsigned short 2 байта, unsigned long long 8 байтов).

В динамически типизированных языках тип переменной интерпретируется во время выполнения и принуждается к типу, в котором он подходит. Как я могу добиться чего-то подобного в C ++?

Моя первая идея – сделать это указателями, в зависимости от его размера я буду хранить номер с соответствующим типом. У этого есть очевидный недостаток в том, чтобы также хранить указатель, но поскольку я предполагаю, что я собираюсь хранить их в куче в любом случае, я не думаю, что это имеет значение.

Я абсолютно уверен, что многие из вас могут дать мне лучшие решения, чем это …

 #include  #include  #include  #include  #include  int main() { std::ifstream f ("input_file"); if (f.is_open()) { std::vector v; unsigned long long int num; while(f >> num) { if (num > std::numeric_limits::max()) { v.push_back(new unsigned long long int(num)); } else { v.push_back(new unsigned short(num)); } } for (auto i: v) { delete i; } f.close(); } } 

Изменить 1: вопрос заключается не в экономии памяти , я знаю, что на динамически типизированных языках необходимое пространство для хранения чисел в этом примере будет больше, чем на C ++, но вопрос не в том, что речь идет об эмуляции динамически набранный язык с некоторым механизмом c ++.

Варианты include …

Дискриминационный союз

Код определяет набор различных поддерживаемых типов T0, T1, T2, T3 … и – концептуально – создает тип управления для

 struct X { enum { F0, F1, F2, F3... } type_; union { T0 t0_; T1 t1_; T2 t2_; T3 t3_; ... }; }; 

Поскольку существуют ограничения на типы, которые могут быть помещены в union s, и если их обходить с использованием new подхода к размещению, необходимо принять меры для обеспечения адекватного выравнивания и правильного вызова деструктора, обобщенная реализация становится более сложной и обычно лучше для использования boost::variant<> . Обратите внимание, что для поля type_ требуется некоторое пространство, union будет не меньше, чем наибольшее из sizeof t0_ , sizeof t1_ … и может потребоваться отступы.

станд :: type_info

Также возможно иметь шаблонный конструктор и оператор присваивания, которые вызывают typeid и записывают std::type_info , что позволяет будущим операциям типа «recover-the-value-if-it-of-the-specific-type». Самый простой способ подобрать это поведение – использовать boost::any .

Полиморфизм времени выполнения

Вы можете создать базовый тип с виртуальным деструктором и любыми функциями, которые вам нужны (например, virtual void output(std::ostream&) ), а затем получить class для каждого из short и long long virtual void output(std::ostream&) . Храните указатели в базовом classе.

Индивидуальные решения

В вашем конкретном сценарии у вас есть только несколько больших чисел: вы можете сделать что-то вроде резервирования одного из short значений, чтобы быть часовым, указывая, что фактическое значение в этой позиции можно воссоздать побитовым сдвигом и ORing следующих 4 ценности. Например…

 10 299 32767 0 0 192 3929 38 

… может кодировать:

 10 299 // 32767 is a sentinel indicating next 4 values encode long long (0 << 48) + (0 << 32) + (192 << 16) + 3929 38 

Концепция здесь похожа на кодировку UTF-8 для международных наборов символов. Это будет очень экономично, но подходит для итерации вперед, а не для произвольного доступа к индексу a la [123] .

Вы можете создать class для хранения динамических значений:

 enum class dyn_type { none_type, integer_type, fp_type, string_type, boolean_type, array_type, // ... }; class dyn { dyn_type type_ = dyn_type::none_type; // Unrestricted union: union { std::int64_t integer_value_; double fp_value_; std::string string_value_; bool boolean_value_; std::vector array_value_; }; public: // Constructors dyn() { type_ = dyn_type::none_type; } dyn(std::nullptr_t) : dyn() {} dyn(bool value) { type_ = dyn_type::boolean_type; boolean_value_ = value; } dyn(std::int32_t value) { type_ = dyn_type::integer_type; integer_value_ = value; } dyn(std::int64_t value) { type_ = dyn_type::integer_type; integer_value_ = value; } dyn(double value) { type_ = dyn_type::fp_type; fp_value_ = value; } dyn(const char* value) { type_ = dyn_type::string_type; new (&string_value_) std::string(value); } dyn(std::string const& value) { type_ = dyn_type::string_type; new (&string_value_) std::string(value); } dyn(std::string&& value) { type_ = dyn_type::string_type; new (&string_value_) std::string(std::move(value)); } // .... // Clear void clear() { switch(type_) { case dyn_type::string_type: string_value_.std::string::~string(); break; //... } type_ = dyn_type::none_type; } ~dyn() { this->clear(); } // Copy: dyn(dyn const&); dyn& operator=(dyn const&); // Move: dyn(dyn&&); dyn& operator=(dyn&&); // Assign: dyn& operator=(std::nullptr_t); dyn& operator=(std::int64_t); dyn& operator=(double); dyn& operator=(bool); // Operators: dyn operator+(dyn const&) const; dyn& operator+=(dyn const&); // ... // Query dyn_type type() const { return type_; } std::string& string_value() { assert(type_ == dyn_type::string_type); return string_value_; } // .... // Conversion explicit operator bool() const { switch(type_) { case dyn_type::none_type: return true; case dyn_type::integer_type: return integer_value_ != 0; case dyn_type::fp_type: return fp_value_ != 0.0; case dyn_type::boolean_type: return boolean_value_; // ... } } // ... }; 

Используется с:

 std::vector xs; xs.push_back(3); xs.push_back(2.0); xs.push_back("foo"); xs.push_back(false); 

Простым способом получить динамическое языковое поведение на C ++ является использование динамического языка, например, для Javascript.

Или, например, библиотека Boost предоставляет интерфейс для Python.

Возможно, это будет иметь дело с набором чисел более эффективным способом, чем вы могли бы сделать сами, но все же он крайне неэффективен по сравнению с использованием только одного общего типа в C ++.

Обычный способ динамического ввода в C ++ – это boost::variant или boost::any .

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