Почему я не могу наследовать от int в C ++? - PullRequest
57 голосов
/ 27 января 2010

Я бы хотел сделать это:

class myInt : public int
{

};

Почему я не могу?

Зачем мне это нужно? Сильнее печатать. Например, я мог бы определить два класса intA и intB, которые позволяют мне делать intA + intA или intB + intB, но не intA + intB.

"Интты не классы." И что?

"У Ints нет данных об участниках." Да, у них есть 32 бита или что-то еще.

"У Ints нет функций-членов." Ну, у них есть целая куча операторов, таких как + и -.

Ответы [ 19 ]

82 голосов
/ 27 января 2010

Комментарий Нила довольно точен. Бьярне упомянул, рассматривая и отвергая эту точную возможность 1 :

Синтаксис инициализатора раньше незаконно для встроенных типов. Позволять это я ввел понятие, что встроенные типы имеют конструкторы и деструкторов. Например:

int a(1);    // pre-2.1 error, now initializes a to 1

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

Разрешение вывод из int не на самом деле дать программист C ++ ничего значительно нового по сравнению с имеющий int член. Это прежде всего потому, что int не имеет любые виртуальные функции для производных класс для переопределения. Более серьезно хотя, правила преобразования C так хаотично, притворяясь, что int, short и т. Д. Ведут себя хорошо обычные занятия не идут на работу. Они либо С совместимы, либо они подчиняться относительно хорошо себя ведет C ++ правила для классов, но не для обоих.

Насколько комментарий оправдывает не делать int классом, это (по крайней мере, в основном) неверно. В Smalltalk все типы являются классами, но почти во всех реализациях Smalltalk есть оптимизации, поэтому реализация может быть практически идентична тому, как вы работали бы с неклассовыми типами. Например, класс smallInteger представляет 15-разрядное целое число, а сообщение «+» жестко запрограммировано в виртуальной машине, поэтому даже если вы можете получить из smallInteger, оно все равно дает производительность, аналогичную встроенному типу ( хотя Smalltalk достаточно отличается от C ++, что прямые сравнения производительности трудны и вряд ли будут много значить).

Один бит, который «теряется» в реализации Smalltalk для smallInteger (причина, по которой он представляет только 15 бит вместо 16), вероятно, не понадобится в C или C ++. Smalltalk немного похож на Java - когда вы «определяете объект», вы на самом деле просто определяете указатель на объект, и вам нужно динамически выделить объект, на который он будет указывать. То, чем вы манипулируете, передаете функцию в качестве параметра и т. Д., Всегда является просто указателем, а не самим объектом.

Это , а не , как реализовано smallInteger - в его случае целочисленное значение помещается непосредственно в то, что обычно будет указателем. Чтобы различать smallInteger и указатель, они заставляют все объекты размещаться на границах четных байтов, поэтому LSB всегда чист. У smallInteger всегда установлен LSB.

Большая часть этого необходима, однако, потому что Smalltalk динамически типизирован - он должен иметь возможность выводить тип, просматривая само значение, а smallInteger в основном использует этот LSB в качестве тега типа. Учитывая, что C ++ является статически типизированным, никогда не требуется выводить тип из значения, поэтому вам, вероятно, не нужно будет «тратить» этот бит на тег типа.


1. В Дизайн и развитие C ++ , §15.11.3.

53 голосов
/ 27 января 2010

Int - это порядковый тип, а не класс. Почему вы хотите?

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

Обновление

@ OP "Ints - это не классы", так?

Наследование , полиморфизм и инкапсуляция являются ключевыми в объектно-ориентированном дизайне . Ничто из этого не относится к порядковым типам. Вы не можете наследовать от int, потому что это просто набор байтов и нет кода.

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

25 голосов
/ 27 января 2010

Зачем мне это нужно? Сильнее печатать. Например, я мог бы определить два класса 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 будет компилироваться. Наследование вызовет именно ту проблему, которую вы хотите избежать. Не наследование позволяет легко избежать проблемы.

"У Ints нет функций-членов." Ну, у них есть целая куча операторов, таких как + и -.

Это не функции-члены.

14 голосов
/ 27 января 2010

Если ОП действительно хочет понять, ПОЧЕМУ C ++ такой, какой он есть, то он должен взять в руки копию книги Страуступа «Дизайн и развитие C ++». Это объясняет обоснование этого и многих других дизайнерских решений на заре C ++.

10 голосов
/ 27 января 2010

Потому что int является нативным типом, а не классом

Редактировать: переместить мои комментарии в мой ответ.

Это происходит из наследия C и того, что представляют собой примитивы. Примитив в c ++ - это просто набор байтов, которые мало что значат, кроме компилятора. Класс, с другой стороны, имеет таблицу функций, и как только вы начинаете идти по пути наследования и виртуального наследования, у вас появляется vtable. Ничего из этого нет в примитиве, и, сделав его таким, вы могли бы: а) сломать большую часть кода на языке c, который предполагает, что значение int имеет только 8 байтов, и б) заставить программы занимать намного больше памяти.

Подумай об этом по-другому. int / float / char не имеет элементов данных или методов. Думайте о примитивах как о кварках - они являются строительными блоками, которые вы не можете подразделить, вы используете их для создания более крупных вещей (извиняюсь, если моя аналогия немного ошибочна, я не знаю достаточно физики элементарных частиц)

9 голосов
/ 27 января 2010

строгая типизация целых чисел (и чисел с плавающей запятой и т. Д.) В 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 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) {}
}

Как только это будет сделано, ваш компилятор сообщит вам о любых местах, которые вы нарушилиправила, закодированные в вышеперечисленных классах.

Я позволю вам самостоятельно разобраться с остальными деталями или купить книгу.

5 голосов
/ 27 января 2010

То, что сказали другие, - правда ... int - это примитив в C ++ (во многом как C #). Тем не менее, вы можете достичь того, что вы хотели, просто построив класс вокруг int:

class MyInt
{
private:
   int mInt;

public:
   explicit MyInt(int in) { mInt = in; }
   // Getters/setters etc
};

Затем вы можете наследовать от этого все, что вам весело.

4 голосов
/ 27 января 2010

Никто не упомянул, что C ++ был разработан, чтобы иметь (в основном) обратную совместимость с C, чтобы упростить путь обновления для C-кодеров, следовательно, struct по умолчанию всем открытым членам и т. Д.

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

3 голосов
/ 27 января 2010

Как и другие, я говорю, не может быть сделано, поскольку тип int является примитивным.

Я понимаю мотивацию, хотя, если это для более сильного набора текста. Для C ++ 0x даже предлагалось, чтобы для этого было достаточно специального вида typedef (но это было отклонено?).

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

template <class Child, class T>
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 <class Child, class T>
Child operator+(Wrapper<Child, T> lhv, Wrapper<Child, T> rhv)
{
    return Wrapper<Child, T>(lhv) += rhv;
}

//Make two different kinds of "int"'s

struct IntA : public Wrapper<IntA, int>
{
    IntA(int n = 0): Wrapper<IntA, int>(n) {}
};

struct IntB : public Wrapper<IntB, int>
{
    IntB(int n = 0): Wrapper<IntB, int>(n) {}
};

#include <iostream>

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

3 голосов
/ 27 января 2010

В C ++ встроенные типы не являются классами.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...