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

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

class myInt : public int
{

};

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

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

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

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

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

Ответы [ 19 ]

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

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

Так сказать, виртуальное наследование - единственная реальная причина, по которой вам все равно нужно наследование; все остальное просто экономит массу времени. И я не думаю, что класс / тип int с виртуальными членами будет самой умной вещью, которую можно себе представить в мире C ++. По крайней мере, не для вас каждый день int.

1 голос
/ 02 мая 2010

Вы можете получить то, что вы хотите с сильными typedefs. См. BOOST_STRONG_TYPEDEF

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

Что значит наследовать от int?

"int" не имеет функций-членов; у него нет данных члена, это 32 (или 64)-битное представление в памяти. У него нет своего собственного vtable. Все, что он «имеет» (на самом деле он даже не владеет ими), это некоторые операторы, такие как + - / *, которые на самом деле являются более глобальными функциями, чем функции-члены.

0 голосов
/ 18 августа 2015

Пожалуйста, извините за мой плохой английский.

Существует большая разница между правильной конструкцией C ++, такой как:

struct Length { double l; operator =!?:%+-*/...(); };
struct Mass { double l; operator =!?:%+-*/...(); };

и предлагаемым расширением

struct Length : public double ;
struct Mass   : public double ;

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

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

Другая проблема связана с количеством ошибок: воспроизведение точно всех действий операторов абсолютно подвержено ошибкам (поскольку явное создание конструктора не запрещает все случаи имплицитов).Вероятность ошибки при построении такого объекта достаточно высока.Это не то же самое, что иметь возможность сделать что-то с помощью тяжелой работы или сделать это уже.

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

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

Можно реализовать хорошее предложение по начальному требованию @Rocketmagnet для целых типов, используя:

enum class MyIntA : long {}; 
auto operator=!?:%+-*/...(MyIntA);
MyIntA operator "" _A(long);

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

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

Вызов Stroustrup @Jerry не имеет значения.Виртуальность имеет значение главным образом для публичного наследования, и здесь есть необходимость в частном наследовании.Рассмотрение вокруг «хаотических» правил преобразования C (есть ли в C ++ 14 что-то не хаотичное?) Базового типа также бесполезно: цель состоит в том, чтобы не иметь правил преобразования по умолчанию, а не следовать стандартным.

0 голосов
/ 02 октября 2014

Этот ответ является реализацией ответа UncleBens

положить в Primitive.hpp

#pragma once

template<typename T, typename Child>
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 <iostream>

using namespace std;
class Integer : public Primitive<int, Integer> {
public:
    Integer(){}
    Integer(int a):Primitive<int, Integer>(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";

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

Если я помню, это было главной или одной из главных причин того, что C ++ не считался истинно объектно-ориентированным языком. Java-люди говорили бы: «В Java ВСЕ - это объект»;)

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

Это связано с тем, как элементы хранятся в памяти. Int в C ++ - это целочисленный тип, как уже упоминалось в другом месте, и он всего 32 или 64 бита (слово) в памяти. Объект, однако, по-разному хранится в памяти. Обычно он хранится в куче и имеет функции, связанные с полиморфизмом.

Я не знаю, как это объяснить лучше. Как бы вы унаследовали от числа 4?

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

Почему вы не можете наследовать от int, даже если захотите?

Performance

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

language     ordinal type boxed type, 
c++          int          ?
java         int          Integer
objective-c  int          NSNumber

Но даже Java и target-c сохраняют свои порядковые типы для использования ... почему?

Простыми причинами являются производительность и потребление памяти. Порядковый тип обычно может быть создан, обработан и передан по значению только в одной или двух инструкциях X86 и в худшем случае потребляет всего несколько байтов. Класс, как правило, не может - он часто использует в 2 или более раз больше памяти, а манипулирование его значением может занять много сотен циклов.

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

Следует отметить, что довольно много языков не имеют порядковых типов, в частности, динамические языки, такие как perl, который почти полностью полагается на тип variadic, который вообще является чем-то другим, и разделяет некоторые накладные расходы. классов.

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

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

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