Why can't I inherit from int in C++?
I'd love to be able to do this:
class myInt : public int
{
};
Why can't I?
Why would I want to? Stronger typing. For example, I could define two classes intA
and intB
, which let me do intA + intA
or intB + intB
, but not intA + intB
.
"Ints aren't classes." So what?
"Ints don't have any member data." Yes they do, they h开发者_JAVA技巧ave 32 bits, or whatever.
"Ints don't have any member functions." Well, they have a whole bunch of operators like +
and -
.
Neil's comment is pretty accurate. Bjarne mentioned considering and rejecting this exact possibility1:
The initializer syntax used to be illegal for built-in types. To allow it, I introduced the notion that built-in types have constructors and destructors. For example:
int a(1); // pre-2.1 error, now initializes a to 1
I considered extending this notion to allow derivation from built-in classes and explicit declaration of built-in operators for built-in types. However, I restrained myself.
Allowing derivation from an
int
doesn't actually give a C++ programmer anything significantly new compared to having anint
member. This is primarily becauseint
doesn't have any virtual functions for the derived class to override. More seriously though, the C conversion rules are so chaotic that pretending thatint
,short
, etc., are well-behaved ordinary classes is not going to work. They are either C compatible, or they obey the relatively well-behaved C++ rules for classes, but not both.
As far as the comment the performance justifies not making int a class, it's (at least mostly) false. In Smalltalk all types are classes -- but nearly all implementations of Smalltalk have optimizations so the implementation can be essentially identical to how you'd make a non-class type work. For example, the smallInteger class is represents a 15-bit integer, and the '+' message is hard-coded into the virtual machine, so even though you can derive from smallInteger, it still gives performance similar to a built-in type (though Smalltalk is enough different from C++ that direct performance comparisons are difficult and unlikely to mean much).
The one bit that's "wasted" in the Smalltalk implementation of smallInteger (the reason it only represents 15 bits instead of 16) probably wouldn't be needed in C or C++. Smalltalk is a bit like Java -- when you "define an object" you're really just defining a pointer to an object, and you have to dynamically allocate an object for it to point at. What you manipulate, pass to a function as a parameter, etc., is always just the pointer, not the object itself.
That's not how smallInteger is implemented though -- in its case, they put the integer value directly into what would normally be the pointer. To distinguish between a smallInteger and a pointer, they force all objects to be allocated at even byte boundaries, so the LSB is always clear. A smallInteger always has the LSB set.
Most of this is necessary, however, because Smalltalk is dynamically typed -- it has to be able to deduce the type by looking at the value itself, and smallInteger is basically using that LSB as a type-tag. Given that C++ is statically typed, there's never a need to deduce the type from the value, so you probably wouldn't need to "waste" that bit on a type-tag.
1. In The Design and Evolution of C++, §15.11.3.
Int is an ordinal type, not a class. Why would you want to?
If you need to add functionality to "int", consider building an aggregate class which has an integer field, and methods that expose whatever additional capabilities that you require.
Update
@OP "Ints aren't classes" so?
Inheritance, polymorphism and encapsulation are keystones of object oriented design. None of these things apply to ordinal types. You can't inherit from an int because it's just a bunch of bytes and has no code.
Ints, chars, and other ordinal types do not have method tables, so there's no way to add methods or override them, which is really the heart of inheritance.
Why would I want to? Stronger typing. For example, I could define two classes intA and intB, which let me do intA+intA or intB+intB, but not intA+intB.
That makes no sense. You can do all that without inheriting from anything. (And on the other hand, I don't see how you could possibly achieve it using inheritance.) For example,
class SpecialInt {
...
};
SpecialInt operator+ (const SpecialInt& lhs, const SpecialInt& rhs) {
...
}
Fill in the blanks, and you have a type that solves your problem. You can do SpecialInt + SpecialInt
or int + int
, but SpecialInt + int
won't compile, exactly as you wanted.
On the other hand, if we pretended that inheriting from int was legal, and our SpecialInt
derived from int
, then SpecialInt + int
would compile. Inheriting would cause the exact problem you want to avoid. Not inheriting avoids the problem easily.
"Ints don't have any member functions." Well, they have a whole bunch of operators like + and -.
Those aren't member functions though.
strong typing of ints (and floats etc) in c++
Scott Meyer (Effective c++ has a very effective and powerful solution to your problem of doing strong typing of base types in c++, and it works like this:
Strong typing is a problem that can be addressed and evaluated at compile time, which means you can use the ordinals (weak typing) for multiple types at run-time in deployed apps, and use a special compile phase to iron out inappropriate combinations of types at compile time.
#ifdef STRONG_TYPE_COMPILE
typedef time Time
typedef distance Distance
typedef velocity Velocity
#else
typedef time float
typedef distance float
typedef velocity float
#endif
You then define your Time
, Mass
, Distance
to be classes with all (and only) the appropriate operators overloaded to the appropriate operations. In pseudo-code:
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) {}
}
Once this is done, your compiler will tell you any places you have violated the rules encoded in the above classes.
I'll let you work out the rest of the details yourself, or buy the book.
Because int is a native type and not a class
Edit: moving my comments into my answer.
It comes from the C heritage and what, exactly, primitives represent. A primitive in c++ is just a collection of bytes that have little meaning except to the compiler. A class, on the other hand, has a function table, and once you start going down the inheritance and virtual inheritance path, then you have a vtable. None of that is present in a primitive, and by making it present you would a) break a lot of c code that assumes an int is 8 bytes only and b) make programs take up a lot more memory.
Think about it another way. int/float/char don't have any data members or methods. Think of the primitives as quarks - they're the building blocks that you can't subdivide, you use them to make bigger things (apologies if my analogy is a little off, I don't know enough particle physics)
No one has mentioned that C++ was designed to have (mostly) backwards compatibility with C, so as to ease the upgrade path for C coders hence struct
defaulting to all members public etc.
Having int
as a base class that you could override would fundamentally complicate that rule no end and make the compiler implementation hellish which if you want existing coders and compiler vendors to support your fledgling language was probably not worth the effort.
What others have said is true... int
is a primitive in C++ (much like C#). However, you can achieve what you wanted by just building a class around int
:
class MyInt
{
private:
int mInt;
public:
explicit MyInt(int in) { mInt = in; }
// Getters/setters etc
};
You can then inherit from that all you jolly want.
As others I saying, can't be done since int is a primitive type.
I understand the motivation, though, if it is for stronger typing. It has even been proposed for C++0x that a special kind of typedef should be enough for that (but this has been rejected?).
Perhaps something could be achieved, if you provided the base wrapper yourself. E.g something like the following, which hopefully uses curiously recurring templates in a legal manner, and requires only deriving a class and providing a suitable constructor:
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';
}
But if you take the advice that you should just define a new type and overload the operators, you might take a look at Boost.Operators
In C++ the built-in types are not classes.
This is a very old topic, but still relevant to many.
Unit-aware programming provides one very important reason why inheriting from intrinsic/fundamental types would be valuable in C++. Numerous well-developed solutions now exist to this problem, but all of them require templates in order to achieve what might otherwise have been handled directly with inheritance, polymorphism, and the strong type-checking of C++. The following is one such alternative for unit-aware programming:
https://benjaminjurke.com/content/articles/2015/compile-time-numerical-unit-dimension-checking/
Inheriting from raw pointers also makes sense (and is also illegal). We can easily create a class where we inherit from a smart pointer thereby extending its behavior, but due to the limitation of C++, we are unable to do the same with a raw pointer. This means that our only way to extend behavior of raw pointers like char* is to write functions that require the pointer to be passed as an argument to the function instead of making them look more like methods you might see within std::string.
Of course, we can always use composition instead of inheritance to get the same effect, but in so-doing, we lose all of the intrinsic operations (such as operator[] and operator++ for a char*) and must remap every one of them that we need to support. Is it doable? Sure. I've done it. Is it easy? Not necessarily. That depends on how much you need to map and how quickly it needs to get done. Is it fast? Depends.
With all of that said, in my view, the biggest argument in favor of inheriting behavior from intrinsic types (including raw pointer types) is that it demonstrates the conceptual consistency of the language. Overall, C++ is very consistent, but it breaks down a bit here in my book as intrinsic types are considered to be special, and frankly, they are not.
Well, you don’t really need to inherit anything which hasn’t got any virtual member functions. So even if int
were a class, there would not be a plus over composition.
So to say, virtual inheritance is the only real reason you’d need inheritance for anyway; everything else is just saving you masses of typing time. And I don’t think an int
class/type with virtual members would be the smartest thing to imagine in the C++ world. At least not for you every day int
.
You can get what you want with strong typedefs. See BOOST_STRONG_TYPEDEF
What does it mean to inherit from an int?
"int" has no member functions; it has no member data, it's a 32 (or 64) bit representation in memory. It doesn't have it's own vtable. All what it "has" (it doesn't really even own them) are some operators like +-/* that are really more global functions than member functions.
This answer is an implementation of UncleBens answer
put in 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);}
};
Example:
#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";
}
More general than the fact that "int is primitive" is this: int
is a scalar type, while classes are aggregate types. A scalar is an atomic value, while an aggregate is something with members. Inheritance (at least as it exists in C++) only makes sense for an aggregate type, because you can't add members or methods to scalars — by definition, they don't have any members.
Please, excuse me for my poor English.
There is a major difference between C++ correct construction like this:
struct Length { double l; operator =!?:%+-*/...(); };
struct Mass { double l; operator =!?:%+-*/...(); };
and the proposed extension
struct Length : public double ;
struct Mass : public double ;
And this difference lies on the keyword this
behavior. this
is a pointer and using a pointer let few chances to use registers for computations, because in usuals processors registers does not have address. Worst, using pointer make the compiler suspicous about the fact that two pointers may designate the same memory.
This will put an extraordinary burden on the compiler to optimize trivials ops.
Another problem is on the number of bugs: reproducing exactly all the behavior of operators is absolutly error prone (for ex making constructor explicit does not forbid all implicits cases). Probability of error while building such an object is quite high. It is not equivalent to have the possibility to do something thru hard work or to have it already done.
Compiler implementors would introduce type checking code (with maybe some errors, but compiler exactness is much better than client code, because of any bug in compiler generate countless errors cases), but main behavior of operation will remain exactly the same, with as few errors than usual.
The proposed alternate solution (using structs during debug phase and real floats when optimized ones) is interesting but has drawbacks: it's raise the probability to have bugs only in optimized version. And debugging optimized application is much costly.
One may implement a good proposal for @Rocketmagnet initial demand for integers types using :
enum class MyIntA : long {};
auto operator=!?:%+-*/...(MyIntA);
MyIntA operator "" _A(long);
The bug level will be quite high, like using single member trick, but compiler will treat thoses types exactly like built-in integers (including register capability & optimisation), thanks for inlining.
But this trick can't be used (sadly) for floating numbers, and the nicest need is obviously real valued dimensions checking. One may not mix up apples and pears: adding length and area is a common error.
Stroustrup' invocation by @Jerry is irrelevant. Virtuality is meaningful mainly for public inheritance, and the need is here toward private inheritance. Consideration around 'chaotic' C conversion rules (Does C++14 have anything not chaotic?) of basic type are also not useful : the objective is to have no default conversion rules, not to follow standard ones.
If I remember, this was the main - or one of - the main reasons C++ was not considered a true object oriented language. Java people would say: "In Java, EVERYTHING is an object" ;)
This is related to how the items are stored in memory. An int in C++ is an integral type, as mentioned elsewhere, and is just 32 or 64 bits (a word) in memory. An object, however, is stored differently in memory. It is usually stored on the heap, and it has functionality related to polymorphism.
I don't know how to explain it any better. How would you inherit from the number 4?
Why can't you inherit from int, even though you might want to?
Performance
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.
精彩评论