为什么我不能从 C++ 中继承 int ?

发布于 2024-08-19 23:07:58 字数 398 浏览 9 评论 0原文

我很想能够做到这一点:

class myInt : public int
{

};

为什么我不能?

我为什么要这么做?打字能力更强。例如,我可以定义两个类 intAintB,这让我可以执行 intA + intAintB + intB >,但不是 intA + intB

“整数不是类。”所以呢?

“整数没有任何成员数据。”是的,他们有,他们有 32 位,或者其他什么。

“整数没有任何成员函数。”嗯,他们有一大堆运算符,例如 +-

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 have 32 bits, or whatever.

"Ints don't have any member functions." Well, they have a whole bunch of operators like + and -.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(20

旧时浪漫 2024-08-26 23:07:59

尼尔的评论非常准确。 Bjarne 提到考虑并拒绝这种确切的可能性1

初始化语法过去是
对于内置类型是非法的。允许
我引入了这样一个概念
内置类型有构造函数和
析构函数。例如:

int a(1); // 2.1 之前的错误,现在将 a 初始化为 1

我考虑将这个概念扩展到
允许从内置类派生
和内置的显式声明
内置类型的运算符。然而,
我克制住了自己。

允许
int 派生不会
实际上给一个C++程序员
与以下相比有什么显着的新变化
有一个 int 成员。这是
主要是因为 int 没有
派生的任何虚函数
要覆盖的类。更认真地
不过,C 的转换规则是这样的
假装int很混乱,
short等表现良好
普通的课程是行不通的。
它们要么是 C 兼容的,要么是
遵守相对良好的C++
类的规则,但不是两者。

至于性能证明不将 int 分类为合理的评论,它(至少大部分)是错误的。在 Smalltalk 中,所有类型都是类——但几乎所有 Smalltalk 的实现都有优化,因此实现基本上与非类类型的工作方式相同。例如,smallInteger 类表示一个 15 位整数,并且“+”消息被硬编码到虚拟机中,因此即使您可以从smallInteger 派生,它仍然提供与内置类型类似的性能(尽管 Smalltalk 与 C++ 有很大不同,直接进行性能比较很困难,而且意义不大)。

在 SmallInteger 的 Smalltalk 实现中“浪费”的一位(因为它只表示 15 位而不是 16 位)在 C 或 C++ 中可能不需要。 Smalltalk 有点像 Java —— 当你“定义一个对象”时,你实际上只是定义了一个指向对象的指针,并且你必须动态分配一个对象来让它指向。您操作的内容、作为参数传递给函数等的内容始终只是指针,而不是对象本身。

但这并不是smallInteger 的实现方式——在这种情况下,它们将整数值直接放入通常是指针的位置。为了区分小整数和指针,它们强制所有对象在偶数字节边界分配,因此 LSB 始终是清晰的。小整数总是设置 LSB。

然而,其中大部分都是必要的,因为 Smalltalk 是动态类型的——它必须能够通过查看值本身来推断类型,而smallInteger 基本上使用该 LSB 作为类型标记。鉴于 C++ 是静态类型的,因此永远不需要从值中推断出类型,因此您可能不需要在类型标记上“浪费”该位。


1. C++ 的设计和演变,§15.11.3。

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 an int member. This is
primarily because int doesn't have
any virtual functions for the derived
class to override. More seriously
though, the C conversion rules are so
chaotic that pretending that int,
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.

少女的英雄梦 2024-08-26 23:07:59

Int 是序数类型,而不是类。你为什么想要这样做?

如果您需要向“int”添加功能,请考虑构建一个具有整数字段的聚合类,以及公开您需要的任何附加功能的方法。

更新

@OP “整数不是类”,所以?

继承、多态性和封装是面向对象设计的基石 >。这些都不适用于序数类型。您不能从 int 继承,因为它只是一堆字节并且没有代码。

整型、字符型和其他序数类型没有方法表,因此无法添加或重写方法,而这正是继承的核心。

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.

七婞 2024-08-26 23:07:59

我为什么要这么做?打字能力更强。例如,我可以定义两个类 intA 和 intB,这让我可以执行 intA+intA 或 intB+intB,但不能执行 intA+intB。

这没有任何意义。您可以在不继承任何内容的情况下完成所有这些操作。 (另一方面,我不知道如何使用继承来实现它。)例如,

class SpecialInt {
 ...
};
SpecialInt operator+ (const SpecialInt& lhs, const SpecialInt& rhs) {
  ...
}

填空,您就有了一个可以解决您的问题的类型。您可以执行 SpecialInt + SpecialIntint + int,但 SpecialInt + int 不会完全按照您想要的方式进行编译。

另一方面,如果我们假装从 int 继承是合法的,并且我们的 SpecialInt 派生自 int,那么 SpecialInt + int 会编译。继承会导致您想要避免的问题。 继承很容易避免这个问题。

“整数没有任何成员函数。”嗯,他们有一大堆运算符,例如 + 和 -。

但这些不是成员函数。

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.

夏花。依旧 2024-08-26 23:07:59

C++ 中整数(和浮点数等)的强类型

Scott Meyer (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

然后将您的 TimeMassDistance 定义为具有所有类型的类。 (并且仅)在伪代码中重载适当的运算符:

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) {}
}

完成此操作后,编译器将告诉您违反上述类中编码的规则的任何地方,

我将让您解决其余的问题。自己了解详细信息,或购买书籍。

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.

末蓝 2024-08-26 23:07:59

因为 int 是本机类型而不是类

编辑:将我的评论移到我的答案中。

它来自 C 遗产以及原语所代表的内容。 C++ 中的原语只是字节的集合,除了对编译器之外没有什么意义。另一方面,类有一个函数表,一旦你开始沿着继承和虚拟继承路径走下去,那么你就有了一个虚函数表。原语中不存在这些,通过使其存在,您将 a) 破坏许多假设 int 仅 8 字节的 c 代码,b) 使程序占用更多内存。

换个角度想一下。 int/float/char 没有任何数据成员或方法。将原语视为夸克 - 它们是你无法细分的构建块,你用它们来制造更大的东西(如果我的类比有点偏离,我很抱歉,我对粒子物理学了解不够)

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)

Oo萌小芽oO 2024-08-26 23:07:59

没有人提到 C++ 被设计为(大部分)向后兼容 C,以便简化 C 编码人员的升级路径,因此 struct 默认为所有成员 public 等

。 /code> 作为您可以重写的基类,将从根本上使该规则变得无休止地复杂化,并使编译器实现变得地狱般,如果您希望现有的编码器和编译器供应商支持您的新兴语言,那么这可能不值得付出努力。

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.

平生欢 2024-08-26 23:07:59

正如我其他人所说,由于 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

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

丢了幸福的猪 2024-08-26 23:07:59

其他人所说的是真的... int 是 C++ 中的原语(很像 C#)。但是,您只需围绕 int 构建一个类即可实现您想要的目标:

class MyInt
{
private:
   int mInt;

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

然后您可以继承您想要的一切。

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.

jJeQQOZ5 2024-08-26 23:07:59

你可以通过强大的 typedef 得到你想要的。请参阅 BOOST_STRONG_TYPEDEF

You can get what you want with strong typedefs. See BOOST_STRONG_TYPEDEF

彻夜缠绵 2024-08-26 23:07:59

这是一个非常古老的话题,但仍然与许多人相关。

单元感知编程提供了一个非常重要的原因,说明为什么从内在/基本类型继承在 C++ 中很有价值。现在针对此问题存在许多成熟的解决方案,但所有这些解决方案都需要模板才能实现本来可以通过继承、多态性和 C++ 的强类型检查直接处理的功能。以下是单位感知编程的一种替代方案:

https://benjaminjurke.com/content/articles/2015/compile-time-numerical-unit-dimension-checking/

从原始指针继承也是有意义的(也是非法的)。我们可以轻松地创建一个类,从智能指针继承,从而扩展其行为,但由于 C++ 的限制,我们无法对原始指针执行相同的操作。这意味着我们扩展像 char* 这样的原始指针行为的唯一方法是编写需要将指针作为参数传递给函数的函数,而不是使它们看起来更像您在 std::string 中看到的方法。

当然,我们总是可以使用组合而不是继承来获得相同的效果,但这样做时,我们会丢失所有内部操作(例如 char* 的operator[]和operator++),并且必须重新映射它们中的每一个我们需要支持。可行吗?当然。我已经做到了。容易吗?未必。这取决于您需要绘制多少地图以及需要多快完成。快吗?视情况而定。

综上所述,在我看来,支持从内部类型(包括原始指针类型)继承行为的最大论据是它证明了语言的概念一致性。总的来说,C++ 是非常一致的,但在我的书中它有点崩溃,因为内在类型被认为是特殊的,坦率地说,它们不是。

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.

眼泪也成诗 2024-08-26 23:07:59

在 C++ 中,内置类型不是类。

In C++ the built-in types are not classes.

淡淡の花香 2024-08-26 23:07:59

好吧,您实际上不需要继承任何没有任何虚拟成员函数的东西。因此,即使 int 一个类,也不会有优于组合的情况。

可以这么说,虚拟继承是您需要继承的唯一真正原因;其他一切都只是节省您大量的打字时间。我不认为带有虚拟成员的 int 类/类型是 C++ 世界中最聪明的事情。至少对你来说不是每天int

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.

半步萧音过轻尘 2024-08-26 23:07:59

从 int 继承是什么意思?

“int”没有成员函数;它没有成员数据,它是内存中的 32(或 64)位表示形式。它没有自己的vtable。它“拥有”的所有东西(它甚至不真正拥有它们)都是像 +-/* 这样的运算符,它们实际上比成员函数更具全局性。

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.

番薯 2024-08-26 23:07:59

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";

}

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";

}
霓裳挽歌倾城醉 2024-08-26 23:07:59

请原谅我糟糕的英语。

之间有一个主要区别

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

像这样的 C++ 正确构造和建议的扩展

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

,这种区别在于关键字 this 的行为。 this 是一个指针,使用指针几乎没有机会使用寄存器进行计算,因为通常处理器寄存器没有地址。最糟糕的是,使用指针会让编译器怀疑两个指针可能指定相同的内存。

这会给编译器带来巨大的负担来优化琐碎的操作。

另一个问题是错误的数量:准确地再现运算符的所有行为绝对容易出错(例如,使构造函数显式并不禁止所有隐式情况)。构建此类对象时出错的概率相当高。它不等于有可能通过努力做某事或已经完成它。

编译器实现者会引入类型检查代码(可能会有一些错误,但编译器的准确性比客户端代码好得多,因为编译器中的任何错误都会产生无数错误情况),但操作的主要行为将保持完全相同,错误很少比平时。

所提出的替代解决方案(在调试阶段使用结构,在优化阶段使用实际浮动)很有趣,但也有缺点:它增加了仅在优化版本中出现错误的可能性。并且调试优化的应用程序的成本很高。

人们可以使用@Rocketmagnet对整数类型的初始需求实现一个很好的建议:

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

错误级别会相当高,就像使用单成员技巧一样,但编译器将完全像内置整数一样对待这些类型(包括寄存器功能和优化) ,感谢内联。

但这个技巧不能(遗憾地)用于浮点数,而且最好的需求显然是实值尺寸检查。人们可能不会混淆苹果和梨:将长度和面积相加是一种常见的错误。

@Jerry 对 Stroustrup 的调用是无关紧要的。虚拟性主要对公共继承有意义,而这里的需要是针对私有继承。围绕基本类型的“混乱”C 转换规则(C++14 有什么不混乱的东西吗?)的考虑也是没有用的:目标是没有默认的转换规则,而不是遵循标准的转换规则。

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.

や三分注定 2024-08-26 23:07:59

AFAICT,所有其他答案都没有抓住重点。

是的,当然,这是不可能的,因为标准是这么说的......

部分问题在于你的问题的结构方式,如果 int 是以您的问题所暗示的方式继承的。

然而,我认为你真正想要/询问为什么它不存在是一个 CRTP/标记的 int 模板:

class MyInt : public int<MyInt>
{
};

这样的东西只是一堆的简写您的自定义类型上的运算符。

这可能不存在,因为:

  • 可能有很多微妙的问题会使其不那么理想
    • 与其他类型的转换可能需要也可能不需要
  • 有可能以另一种方式获得相同的效果(尽管需要更多的工作),
  • 在编译器中实现可能会很痛苦(很多事情过去由于困难而没有实现) )
  • 模板稍后出现。
  • 因为当时没有人想到这一点。

AFAICT, all the other answers miss the point.

Yes, of course, it's not possible because it's the standard says so...

Part of the problem is the way your question is structured, you wouldn't get what you are claiming to want if int was inheritable in the way that seems to be implied by your question.

However, I think what you really are wishing-for/asking-why-it-doesn't-exist is a CRTP/tagged int template:

class MyInt : public int<MyInt>
{
};

Such a thing would simply be a short hand for a bunch of operators on your custom type.

That probably doesn't exist because:

  • there's probably a lot of subtle gotchas that would make it less desirable
    • conversion to/from other types may or may not desirable
  • it was possible to get the same effect another way (though with much more work)
  • might have been a pain to implement in the compilers (lots of things used to not get implemented because of difficulty)
  • templates came a bit later.
  • because no one thought of it at the time.
婴鹅 2024-08-26 23:07:59

比“int 是原始类型”这一事实更普遍的是:int标量类型,而类是聚合类型。标量是原子值,而聚合是具有成员的东西。继承(至少在 C++ 中存在)仅对聚合类型有意义,因为您无法向标量添加成员或方法 — 根据定义,它们没有任何成员。

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.

看轻我的陪伴 2024-08-26 23:07:59

如果我记得的话,这是 C++ 不被认为是真正的面向对象语言的主要原因(或者说主要原因之一)。 Java 人会说:“在 Java 中,一切都是对象”;)

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" ;)

墨洒年华 2024-08-26 23:07:59

这与项目在内存中的存储方式有关。正如其他地方提到的,C++ 中的 int 是整型,并且在内存中只是 32 或 64 位(一个字)。然而,对象在内存中的存储方式不同。它通常存储在堆上,并且具有与多态性相关的功能。

我不知道如何更好地解释它。你会如何继承数字4?

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?

甜`诱少女 2024-08-26 23:07:59

为什么你不能从 int 继承,即使你可能想继承?

性能

没有任何功能上的原因可以解释为什么你不能(在任意语言中)继承序数类型,如 int、char 或 char* 等。一些语言,如 Java 和 Objective-C 实际上提供了类/对象(盒装) )基本类型的版本,以满足这种需求(以及处理序数类型不是对象的其他一些令人不快的后果):

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

但即使 Java 和 Objective-C 也保留其序数类型以供使用......为什么?

原因很简单,就是性能和内存消耗。序数类型通常只需一两条 X86 指令即可构造、操作和按值传递,并且最坏情况下也只消耗几个字节。类通常不能 - 它通常使用两倍或更多的内存,并且操作其值可能需要数百个周期。

这意味着了解这一点的程序员通常会使用序数类型来实现性能或内存使用敏感的代码,并要求语言开发人员支持基本类型。

应该注意的是,相当多的语言没有序数类型,特别是像 Perl 这样的动态语言,它几乎完全依赖于可变参数类型,这完全是另外一回事,并且共享一些课程的开销。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文