Почему я не могу наследовать от int в C ++?
Мне бы хотелось сделать это:
class myInt : public int { };
Почему я не могу?
Почему я хочу? Более сильный ввод. Например, я мог бы определить два classа intA
и intB
, которые позволяют мне делать intA + intA
или intB + intB
, но не intA + intB
.
- Почему добавление null в строку законно?
- Почему частные поля являются приватными для типа, а не экземпляра?
- Почему C ++ нужен отдельный заголовочный файл?
- C ++: обоснование правила скрытия
- C # static member "inheritance" - почему это вообще существует?
«Инты – это не classы». И что?
«У Ints нет данных о членах». Да, они есть, у них 32 бита или что-то еще.
«Интс не имеет каких-либо функций-членов». Ну, у них есть целая группа операторов вроде +
и -
.
- Почему переменная Iteration в инструкции C # foreach доступна только для чтения?
- Перегрузка функций по типу возврата?
- Нумерация нулевого месяца
- Почему шаблон функции не может быть частично специализированным?
- Почему последняя часть имени объекта Objective-C принимает аргумент (когда имеется более одной части)?
- Почему логика типа F # настолько непостоянна?
- Почему ссылки не переустанавливаются в C ++
- Синтаксис для универсальных ссылок
Комментарий Нейла довольно точен. Бьярн упомянул об этом и отверг эту точную возможность 1 :
Синтаксис инициализатора был незаконным для встроенных типов. Чтобы это разрешить, я представил понятие о том, что встроенные типы имеют конструкторы и деструкторы. Например:
int a(1); // pre-2.1 error, now initializes a to 1
Я решил расширить это понятие, чтобы позволить вывод из встроенных classов и явное объявление встроенных операторов для встроенных типов. Однако я сдержался.
Разрешение вывода из
int
фактически не дает программисту на C ++ ничего нового по сравнению с наличием членаint
. Это связано прежде всего с тем, чтоint
не имеет виртуальных функций для переопределения производного classа. Более серьезно, однако, правила конверсии C настолько хаотичны, что притворяться, чтоint
,short
и т. Д. Являются обычными classами с хорошим поведением, не будут работать. Они либо совместимы с C, либо подчиняются относительно хорошим правилам C ++ для classов, но не для обоих.
Что касается комментария, то производительность оправдывает не создание int class, а (по крайней мере, в основном) false. В Smalltalk все типы являются classами, но почти во всех реализациях Smalltalk есть оптимизации, поэтому реализация может быть практически идентична тому, как вы делаете работу неclassического типа. Например, class smallInteger представляет собой 15-битное целое число, а сообщение «+» жестко закодировано в виртуальной машине, поэтому, хотя вы можете получить от smallInteger, он все равно дает производительность, похожую на встроенный тип ( хотя Smalltalk достаточно отличается от C ++, что прямые сравнения производительности сложны и вряд ли означают много).
Изменить: один бит, «потерянный» в реализации Smalltalk smallInteger, вероятно, не понадобится на C или C ++. Smalltalk немного похож на Java – когда вы «определяете объект», вы действительно просто определяете указатель на объект, и вам нужно динамически выделять объект для его указания. То, что вы манипулируете, переходите к функции в качестве параметра и т. Д., Всегда является указателем, а не самим объектом.
Это не так, как smallInteger реализован, но в своем случае они помещают целочисленное значение непосредственно в то, что обычно является указателем. Чтобы различать smallInteger и указатель, они заставляют все объекты распределяться на границах байтов, поэтому LSB всегда ясен. У smallInteger всегда есть набор LSB.
Однако большинство из них необходимо, поскольку Smalltalk динамически типизирован – он должен иметь возможность выводить тип, просматривая само значение, а smallInteger в основном использует этот LSB как тег типа. Учитывая, что C ++ статически типизирован, никогда не нужно выводить тип из значения, поэтому вам, вероятно, не нужно будет тратить этот бит.
1 В « Проекте и эволюции C ++» , §15.11.3.
Int – это порядковый тип, а не class. Зачем вам это нужно?
Если вам нужно добавить функциональность в «int», подумайте о создании агрегатного classа с целым полем и методов, которые раскрывают любые дополнительные возможности, которые вам нужны.
Обновить
@OP “Интс не classы” так?
Наследование , polymorphism и инкапсуляция являются краеугольными камнями объектно-ориентированного проектирования . Ни одна из этих вещей не относится к порядковым типам. Вы не можете наследовать от int, потому что это всего лишь куча байтов и не имеет кода.
Ints, chars и другие порядковые типы не имеют таблиц методов , поэтому нет способа добавлять методы или переопределять их, что на самом деле является сердцем наследования.
Почему я хочу? Более сильный ввод. Например, я мог бы определить два classа intA и intB, которые позволяют мне делать intA + intA или intB + intB, но не intA + intB.
Это бессмысленно. Вы можете делать все это, не наследуя ни от чего. (И, с другой стороны, я не вижу, как вы могли бы достичь этого, используя наследование.) Например,
class SpecialInt { ... }; SpecialInt operator+ (const SpecialInt& lhs, const SpecialInt& rhs) { ... }
Заполните пробелы, и у вас есть тип, который решает вашу проблему. Вы можете делать SpecialInt + SpecialInt
или int + int
, но SpecialInt + int
не будет компилироваться точно так, как вы хотели.
С другой стороны, если мы притворились, что наследование из int является законным, а наш SpecialInt
полученный из int
, тогда будет скомпилирован SpecialInt + int
. Наследование приведет к точной проблеме, которую вы хотите избежать. Не наследование позволяет легко устранить проблему.
«Интс не имеет каких-либо функций-членов». Ну, у них есть целая группа операторов вроде + и -.
Тем не менее они не являются функциями-членами.
Если ОП действительно хочет понять, ПОЧЕМУ C ++ – так оно и есть, тогда он должен получить копию книги Stroustup «The Design and Evolution of C ++». Это объясняет обоснование этого и многих других дизайнерских решений в первые дни C ++.
Поскольку int – это родной тип, а не class
Изменить: перевод моих комментариев в мой ответ.
Это происходит из наследия C и что, собственно, представляют примитивы. Примитив в c ++ – это просто набор байтов, которые имеют мало смысла, кроме компилятора. С другой стороны, class имеет таблицу функций, и как только вы начнете спускать путь наследования и виртуального наследования, у вас есть таблица vtable. Ничто из этого не присутствует в примитиве, и, представив его, вы должны: a) разорвать много c-кода, который предполагает, что int имеет только 8 байтов и b) делает программы занимают намного больше памяти.
Подумайте об этом по-другому. У int / float / char нет данных или методов. Подумайте о примитивах как кварках – они являются строительными блоками, которые вы не можете разделить, вы используете их для создания больших вещей (извинения, если моя аналогия немного неактуальна, я не знаю достаточной физики частиц)
сильная типизация ints (и float и т. д.) в c ++
Скотт Мейер ( Effective c ++ имеет очень эффективное и мощное решение вашей проблемы сильной типизации базовых типов в c ++, и он работает следующим образом:
Сильная типизация – это проблема, которую можно решить и оценить во время компиляции , что означает, что вы можете использовать ординалы (слабое типирование) для нескольких типов во время выполнения в развернутых приложениях и использовать специальную фазу компиляции, чтобы сгладить несоответствующие комбинации типов во время компиляции.
#ifdef STRONG_TYPE_COMPILE typedef time Time typedef distance Distance typedef velocity Velocity #else typedef time float typedef distance float typedef velocity float #endif
Затем вы определяете свое Time
, Mass
, Distance
чтобы быть classами со всеми (и только) соответствующими операторами, перегруженными соответствующими операциями. В псевдокоде:
class Time { public: float value; Time operator +(Time b) {self.value + b.value;} Time operator -(Time b) {self.value - b.value;} // don't define Time*Time, Time/Time etc. Time operator *(float b) {self.value * b;} Time operator /(float b) {self.value / b;} } class Distance { public: float value; Distance operator +(Distance b) {self.value + b.value;} // also -, but not * or / Velocity operator /(Time b) {Velocity( self.value / b.value )} } class Velocity { public: float value; // appropriate operators Velocity(float a) : value(a) {} }
Как только это будет сделано, ваш компилятор скажет вам, в каких местах вы нарушили правила, закодированные в вышеуказанных classах.
Я позволю вам самостоятельно разобраться с остальными деталями или купить книгу.
То, что говорили другие, истинно … int
является примитивным в C ++ (как C #). Однако вы можете достичь того, чего хотите, просто построив class вокруг int
:
class MyInt { private: int mInt; public: explicit MyInt(int in) { mInt = in; } // Getters/setters etc };
Затем вы можете наследовать все, что вам нужно.
Никто не упомянул, что C ++ был разработан, чтобы иметь (в основном) обратную совместимость с C, чтобы облегчить путь обновления для C-кодировщиков, поэтому struct
не соответствует всем членам общественности и т. Д.
Наличие int
в качестве базового classа, которое вы могли бы переопределить, в корне усложняло бы это правило без конца и сделало бы реализацию компилятора адским, которая, если вы хотите, чтобы существующие кодеры и поставщики компиляторов поддерживали ваш неоперившийся язык, вероятно, не стоили усилий.
В C ++ встроенные типы не являются classами.
Как я уже говорил, это невозможно, так как int является примитивным типом.
Я понимаю мотивацию, хотя, если это для более сильного набора текста. Для C ++ 0x даже было предложено, чтобы для этого было достаточно специального типа typedef (но это было отклонено?).
Возможно, что-то может быть достигнуто, если вы предоставили базовую оболочку самостоятельно. Например, что-то вроде следующего, которое, мы надеемся, использует любопытно повторяющиеся шаблоны законным образом и требует только получения classа и предоставления подходящего конструктора:
template class Wrapper { T n; public: Wrapper(T n = T()): n(n) {} T& value() { return n; } T value() const { return n; } Child operator+= (Wrapper other) { return Child(n += other.n); } //... many other operators }; template Child operator+(Wrapper lhv, Wrapper rhv) { return Wrapper(lhv) += rhv; } //Make two different kinds of "int"'s struct IntA : public Wrapper { IntA(int n = 0): Wrapper(n) {} }; struct IntB : public Wrapper { IntB(int n = 0): Wrapper(n) {} }; #include int main() { IntA a1 = 1, a2 = 2, a3; IntB b1 = 1, b2 = 2, b3; a3 = a1 + a2; b3 = b1 + b2; //a1 + b1; //bingo //a1 = b1; //bingo a1 += a2; std::cout << a1.value() << ' ' << b3.value() << '\n'; }
Но если вы примете совет, что вы должны просто определить новый тип и перегрузить операторов, вы можете взглянуть на Boost.Operators
Ну, вам действительно не нужно наследовать ничего, у которого нет виртуальных функций-членов. Поэтому, даже если int
был classом, не было бы плюса над составом.
Так сказать, виртуальное наследование – единственная реальная причина, по которой вам нужно наследование в любом случае; все остальное просто экономит массу времени набрав. И я не думаю, что int
class / type с виртуальными членами будет самой умной возможностью представить в мире C ++. По крайней мере, не для вас каждый день int
.
Что значит наследовать от int?
«int» не имеет функций-членов; он не имеет данных-членов, это 32-битное представление в памяти. У него нет собственного vtable. Все, что у него есть “(на самом деле они не принадлежат), есть некоторые операторы, такие как + – / *, которые являются действительно более глобальными функциями, чем функции-члены.
Вы можете получить то, что хотите, с сильными typedef. См. BOOST_STRONG_TYPEDEF
Более общий, чем тот факт, что «int является примитивным»: int
– это скалярный тип, а classы – агрегированные . Скаляр является атомным значением, а совокупность – чем-то с членами. Наследование (по крайней мере, поскольку оно существует в C ++) имеет смысл только для совокупного типа, потому что вы не можете добавлять члены или методы в скаляры – по определению у них нет членов.
Этот ответ – это реализация ответа UncleBens
положить в Primitive.hpp
#pragma once template class Primitive { protected: T value; public: // we must type cast to child to so // a += 3 += 5 ... and etc.. work the same way // as on primitives Child &childRef(){ return *((Child*)this); } // you can overload to give a default value if you want Primitive(){} explicit Primitive(T v):value(v){} T get(){ return value; } #define OP(op) Child &operator op(Child const &v){\ value op v.value; \ return childRef(); \ } // all with equals OP(+=) OP(-=) OP(*=) OP(/=) OP(<<=) OP(>>=) OP(|=) OP(^=) OP(&=) OP(%=) #undef OP #define OP(p) Child operator p(Child const &v){\ Child other = childRef();\ other p ## = v;\ return other;\ } OP(+) OP(-) OP(*) OP(/) OP(<<) OP(>>) OP(|) OP(^) OP(&) OP(%) #undef OP #define OP(p) bool operator p(Child const &v){\ return value p v.value;\ } OP(&&) OP(||) OP(<) OP(<=) OP(>) OP(>=) OP(==) OP(!=) #undef OP Child operator +(){return Child(value);} Child operator -(){return Child(-value);} Child &operator ++(){++value; return childRef();} Child operator ++(int){ Child ret(value); ++value; return childRef(); } Child operator --(int){ Child ret(value); --value; return childRef(); } bool operator!(){return !value;} Child operator~(){return Child(~value);} };
Пример:
#include "Primitive.hpp" #include using namespace std; class Integer : public Primitive { public: Integer(){} Integer(int a):Primitive(a) {} }; int main(){ Integer a(3); Integer b(8); a += b; cout << a.get() << "\n"; Integer c; c = a + b; cout << c.get() << "\n"; cout << (a > b) << "\n"; cout << (!b) << " " << (!!b) << "\n"; }
Пожалуйста, извините меня за моего бедного английского.
Существует большая разница между правильной конструкцией C ++:
struct Length { double l; operator =!?:%+-*/...(); }; struct Mass { double l; operator =!?:%+-*/...(); };
и предлагаемое продление
struct Length : public double ; struct Mass : public double ;
И это различие заключается в ключевом слове this
поведения. this
указатель и использование указателя позволяют мало шансов использовать регистры для вычислений, потому что в обычных процессорах регистры не имеют адреса. Хуже, используя указатель, сделать компилятор подозрительным в том, что два указателя могут обозначать одну и ту же память.
Это поставит чрезвычайную нагрузку на компилятор для оптимизации тривиальных операций.
Еще одна проблема заключается в количестве ошибок: воспроизведение точно всех действий операторов является абсолютной ошибкой (для ex, что делает конструктор явным, не запрещает все случаи implicits). Вероятность ошибки при построении такого объекта довольно высока. Это не эквивалентно тому, чтобы иметь возможность сделать что-то через тяжелую работу или сделать это уже сделано.
Разработчики компилятора будут вводить код проверки типов (с некоторыми ошибками, но точность компилятора намного лучше, чем клиентский код, из-за какой-либо ошибки в компиляторе генерируются бесчисленные ошибки), но основное поведение операции останется таким же, с небольшим количеством ошибок чем обычно.
Предлагаемое альтернативное решение (с использованием структур во время фазы отладки и реальных поплавков при оптимизации) интересно, но имеет свои недостатки: это повышает вероятность наличия ошибок только в оптимизированной версии. Оптимизированное приложение для отладки является дорогостоящим.
Можно реализовать хорошее предложение для первоначального спроса @Rocketmagnet для целых типов, используя:
enum class MyIntA : long {}; auto operator=!?:%+-*/...(MyIntA); MyIntA operator "" _A(long);
Уровень ошибки будет довольно высоким, например, с помощью трюка с одним членом, но компилятор будет обрабатывать типы thoses точно так же, как встроенные целые числа (включая возможность регистрации и оптимизацию), спасибо за inlining.
Но этот трюк нельзя использовать (к сожалению) для плавающих чисел, и самая приятная потребность – это, очевидно, проверка достоверных размеров. Нельзя смешивать яблоки и груши: добавление длины и площади является общей ошибкой.
Вызов Строуструпа by @Jerry не имеет значения. Виртуальность имеет смысл главным образом для общественного наследования, и потребность здесь в частном наследовании. Рассмотрение вокруг «хаотических» правил преобразования C (имеет ли C ++ 14 что-то не хаотичное?) Базового типа также не полезно: цель состоит в том, чтобы не иметь правил преобразования по умолчанию, а не следовать стандартным.
Если я помню, это было главным – или одним из – основные причины, по которым C ++ не считался истинным объектно-ориентированным языком. Java-люди скажут: «В Java, ВСЕ – это объект»;)
Это связано с тем, как элементы хранятся в памяти. Int в C ++ является интегральным типом, как упоминалось в другом месте, и это всего лишь 32 или 64 бита (слово) в памяти. Однако объект хранится по-разному в памяти. Он обычно хранится в куче, и он имеет функциональность, связанную с polymorphismом.
Я не знаю, как это объяснить. Как вы наследуете номер 4?
Why can’t you inherit from int, even though you might want to?
Представление
There’s no functional reason why you shouldn’t be able (in an arbitrary language) inherit from ordinal types such as int, or char, or char* etc. Some languages such as Java and Objective-C actually provide class/object (boxed) versions of the base type, in order to satisfy this need (as well as to deal with some other unpleasant consequences of ordinal types not being objects):
language ordinal type boxed type, c++ int ? java int Integer objective-c int NSNumber
But even Java and objective-c preserve their ordinal types for use… why?
The simple reasons are performance and memory consumption. An ordinal type can be typically be constructed, operated upon, and passed by value in just one or two X86 instructions, and only consumes a few bytes at worst. A class typically cannot – it often uses 2x or more as much memory, and manipulating its value may take many hundreds of cycles.
This means programmers who understand this will typically use the ordinal types to implement performance or memory usage sensitive code, and will demand that language developers support the base types.
It should be noted that quite a few languages do not have ordinal types, in particular the dynamic languages such as perl
, which relies almost entirely on a variadic type, which is something else altogether, and shares some of the overhead of classes.