75

私はこれができるようになりたいです:

class myInt : public int
{

};

なぜできないのですか?

なぜ私はしたいのですか?より強力なタイピング。たとえば、2つのクラスintAとを定義できます。これにより、またはを実行できますが、はできintBません。intA + intAintB + intBintA + intB

「Intはクラスではありません。」だから何?

「Intにはメンバーデータがありません。」はい、そうです、32ビットか何かを持っています。

「Intにはメンバー関数がありません。」ええと、彼らにはとのようなたくさんの演算子が+あり-ます。

4

19 に答える 19

88

ニールのコメントはかなり正確です。Bjarneは、この正確な可能性を検討し、拒否することに言及しました1

初期化構文は、組み込み型では無効でした。それを可能にするために、組み込み型にはコンストラクタとデストラクタがあるという概念を導入しました。例えば:

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

この概念を拡張して、組み込みクラスからの派生と、組み込み型の組み込み演算子の明示的な宣言を可能にすることを検討しました。しかし、私は自分を抑えました。

からの派生を許可しても、実際には、メンバーintを持つ場合と比較して、C++プログラマーに大きな新しいものは何も与えられません。intこれは主intに、派生クラスがオーバーライドする仮想関数がないためです。しかし、もっと深刻なことに、C変換ルールは非常に混沌としているため、、intなど shortが正常に動作する通常のクラスであるかのように振る舞うことはできません。これらはC互換であるか、クラスの比較的正常に動作するC ++ルールに従いますが、両方ではありません。

パフォーマンスがintをクラスにしないことを正当化するコメントに関する限り、それは(少なくともほとんど)誤りです。Smalltalkでは、すべての型がクラスですが、Smalltalkのほぼすべての実装には最適化が含まれているため、実装は、非クラス型を機能させる方法と基本的に同じです。たとえば、smallIntegerクラスは15ビット整数を表し、「+」メッセージは仮想マシンにハードコードされているため、smallIntegerから派生できても、組み込み型と同様のパフォーマンスが得られます(ただし、SmalltalkはC ++とは十分に異なるため、パフォーマンスを直接比較することは難しく、あまり意味がありません)。

smallIntegerのSmalltalk実装で「無駄になった」1ビット(16ビットではなく15ビットしか表さない理由)は、CまたはC++ではおそらく必要ありません。SmalltalkはJavaに少し似ています。「オブジェクトを定義する」ときは、実際にはオブジェクトへのポインタを定義しているだけであり、オブジェクトが指すようにオブジェクトを動的に割り当てる必要があります。操作したり、パラメーターとして関数に渡したりするのは、オブジェクト自体ではなく、常に単なるポインターです。

ただし、これはsmallIntegerの実装方法ではありません。その場合、整数値を通常はポインターになるものに直接配置します。smallIntegerとポインタを区別するために、すべてのオブジェクトが偶数バイト境界で割り当てられるように強制するため、LSBは常にクリアです。smallIntegerには常にLSBが設定されています。

ただし、Smalltalkは動的に型付けされるため、これのほとんどが必要です。値自体を調べて型を推測できる必要があり、smallIntegerは基本的にそのLSBを型タグとして使用します。C ++が静的に型付けされていることを考えると、値から型を推測する必要はないので、おそらく型タグのそのビットを「無駄にする」必要はありません。


1. C ++の設計と進化、§15.11.3。

于 2010-01-27T00:22:56.100 に答える
52

Intは序数型であり、クラスではありません。なぜあなたはしたいですか?

「int」に機能を追加する必要がある場合は、整数フィールドを持つ集約クラスと、必要な追加機能を公開するメソッドを作成することを検討してください。

アップデート

@OP 「Intはクラスではない」そうですか?

継承、ポリモーフィズム、およびカプセル化は、オブジェクト指向設計の要です。これらのことはいずれも序数型には当てはまりません。intは単なるバイトの集まりであり、コードがないため、intから継承することはできません。

Ints、chars、およびその他の序数型にはメソッドテーブルがないため、メソッドを追加したりオーバーライドしたりする方法はありません。これは、実際には継承の中心です。

于 2010-01-26T22:10:35.623 に答える
24

なぜ私はしたいのですか?より強力なタイピング。たとえば、intAとintBの2つのクラスを定義できます。これにより、intA+intAまたはintB+intBは実行できますが、intA+intBは実行できません。

それは意味がありません。何も継承せずにすべてを行うことができます。(一方、継承を使用してどのようにそれを達成できるかはわかりません。)たとえば、

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

空欄に記入すれば、問題を解決するタイプができあがります。SpecialInt + SpecialIntまたはすることはできますがint + intSpecialInt + int希望どおりにコンパイルすることはできません。

一方、intからの継承が合法であると偽って、からSpecialInt派生したint場合SpecialInt + int 、コンパイルされます。継承すると、避けたい正確な問題が発生します。継承しないことで問題を簡単に回避できます。

「Intにはメンバー関数がありません。」ええと、彼らは+や-のようなたくさんの演算子を持っています。

ただし、これらはメンバー関数ではありません。

于 2010-01-26T22:48:40.077 に答える
13

C ++でのint(およびfloatなど)の強い型付け

Scott MeyerEffective 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、すべての(そして唯一の)適切な演算子が適切な操作にオーバーロードされたクラスにしますMassDistance擬似コードの場合:

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

これが行われると、コンパイラは、上記のクラスでエンコードされたルールに違反した場所を通知します。

残りの詳細は自分で解決するか、本を購入します。

于 2010-01-27T00:43:49.093 に答える
10

intはネイティブ型であり、クラスではないため

編集:私のコメントを私の答えに移動します。

それはCの遺産と、正確にはプリミティブが表すものに由来します。C ++のプリミティブは、コンパイラ以外にはほとんど意味のないバイトのコレクションです。一方、クラスには関数テーブルがあり、継承と仮想継承のパスをたどり始めると、vtableができます。そのどれもプリミティブには存在しません。それを存在させることにより、a)intが8バイトのみであると想定する多くのcコードを壊し、b)プログラムがより多くのメモリを消費するようにします。

別の方法で考えてください。int / float / charには、データメンバーまたはメソッドはありません。プリミティブをクォークと考えてください-それらは細分化できないビルディングブロックであり、より大きなものを作るためにそれらを使用します(私のアナロジーが少しずれている場合はお詫びします、私は十分な素粒子物理学を知りません)

于 2010-01-26T22:08:54.363 に答える
5

C ++がCとの(ほとんど)下位互換性を持つように設計されていることについては誰も言及していません。これにより、Cコーダーのアップグレードパスが容易になり、structデフォルトですべてのメンバーがパブリックになります。

基本クラスintとしてオーバーライドできるものがあると、そのルールが根本的に複雑になり、コンパイラーの実装が地獄になります。既存のコーダーやコンパイラーベンダーに、駆け出しの言語をサポートさせたい場合は、おそらく努力する価値がありません。

于 2010-01-27T00:35:07.720 に答える
4

他の人が言ったことは本当です... intC++のプリミティブです(C#によく似ています)。ただし、次のクラスを構築するだけで、目的を達成できますint

class MyInt
{
private:
   int mInt;

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

その後、あなたはそれからあなたが愉快に望むすべてを継承することができます。

于 2010-01-26T22:24:15.400 に答える
4

他の人が言っているように、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を参照してください。

于 2010-01-26T23:22:22.520 に答える
3

C ++では、組み込み型はクラスではありません。

于 2010-01-26T22:08:41.370 に答える
2

強力なtypedefを使用して必要なものを取得できます。BOOST_STRONG_TYPEDEFを参照してください

于 2010-05-02T13:59:51.463 に答える
2

ええと、仮想メンバー関数を持たないものを実際に継承する必要はありません。ですから、たとえクラスint だったとしても、構成にプラスはありません。

つまり、仮想継承は、とにかく継承が必要になる唯一の本当の理由です。他のすべてはあなたにタイピング時間の大部分を節約しているだけです。intそして、仮想メンバーを持つクラス/タイプは、C++の世界で想像するのに最も賢いものではないと思います。少なくともあなたにとっては毎日ではありませんint

于 2010-01-27T00:36:40.937 に答える
2

これは非常に古いトピックですが、それでも多くの人に関係があります。

ユニット対応プログラミングは、C++で組み込み型/基本型から継承することが価値があるという非常に重要な理由の1つです。現在、この問題に対して多くの十分に開発されたソリューションが存在しますが、継承、ポリモーフィズム、およびC ++の強力な型チェックで直接処理された可能性があるものを実現するには、それらすべてにテンプレートが必要です。以下は、ユニット対応プログラミングのそのような代替手段の1つです。

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

生のポインタから継承することも理にかなっています(そしてまた違法です)。スマートポインタから継承するクラスを簡単に作成してその動作を拡張できますが、C ++の制限により、生のポインタで同じことを行うことはできません。つまり、char *のようなrawポインターの動作を拡張する唯一の方法は、std :: string内に表示されるメソッドのように見せるのではなく、ポインターを関数への引数として渡す必要がある関数を作成することです。

もちろん、継承の代わりにコンポジションを使用して同じ効果を得ることができますが、そうすると、すべての固有の操作(char*のoperator[]やoperator++など)が失われ、すべてを再マップする必要があります。サポートする必要があること。それは実行可能ですか?もちろん。私はそれをしました。簡単ですか?必ずしも。それは、マップする必要がある量と、それを実行するために必要な速度によって異なります。速いですか?依存します。

とはいえ、私の見解では、組み込み型(生のポインター型を含む)から動作を継承することを支持する最大の議論は、言語の概念的な一貫性を実証することです。全体として、C ++は非常に一貫性がありますが、組み込み型は特殊であると見なされているため、私の本では少し壊れていますが、率直に言ってそうではありません。

于 2021-10-15T02:33:48.267 に答える
1

intから継承するとはどういう意味ですか?

「int」にはメンバー関数はありません。メンバーデータはなく、メモリ内の32(または64)ビット表現です。独自のvtableはありません。それが「持っている」(実際にはそれらを所有していません)すべては、メンバー関数よりも実際にはよりグローバルな関数である+-/*のようないくつかの演算子です。

于 2010-01-26T22:13:11.110 に答える
1

この回答は、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";

}
于 2014-10-02T03:07:01.270 に答える
0

「intはプリミティブ」であるという事実よりも一般的です。これintスカラー型ですが、クラスは集約型です。スカラーはアトミック値ですが、アグリゲートはメンバーを持つものです。継承(少なくともC ++に存在する場合)は、スカラーにメンバーまたはメソッドを追加できないため、集約型に対してのみ意味があります。定義上、それらにはメンバーがありません。

于 2010-01-26T23:40:34.357 に答える
0

失礼します。英語が下手です。

このようなC++の正しい構造には大きな違いがあります。

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

および提案された拡張

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

そして、この違いはキーワードのthis振る舞いにあります。thisはポインタであり、ポインタを使用すると、通常のプロセッサではレジスタにアドレスがないため、計算にレジスタを使用する機会がほとんどありません。最悪の場合、ポインタを使用すると、2つのポインタが同じメモリを指定する可能性があるという事実についてコンパイラが疑わしくなります。

これは、些細な操作を最適化するためにコンパイラに特別な負担をかけます。

もう1つの問題は、バグの数です。演算子のすべての動作を正確に再現すると、絶対にエラーが発生しやすくなります(たとえば、コンストラクターを明示的にすると、すべての暗黙的なケースが禁止されるわけではありません)。このようなオブジェクトの作成中にエラーが発生する可能性は非常に高くなります。大変な仕事を通して何かをする可能性があること、またはそれをすでに行っていることと同等ではありません。

コンパイラの実装者は型チェックコードを導入しますが(エラーが発生する可能性がありますが、コンパイラのバグにより無数のエラーが発生するため、コンパイラの正確性はクライアントコードよりもはるかに優れています)、動作の主な動作はまったく同じであり、エラーはほとんどありません。いつもより。

提案された代替ソリューション(デバッグフェーズで構造体を使用し、最適化されたもので実際のフロートを使用する)は興味深いものですが、欠点があります。最適化されたバージョンでのみバグが発生する可能性が高くなります。また、最適化されたアプリケーションのデバッグには非常にコストがかかります。

以下を使用して、整数型の@Rocketmagnet初期需要に対する適切な提案を実装できます。

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

バグレベルは、単一メンバートリックを使用する場合と同様に非常に高くなりますが、コンパイラは、インライン化のおかげで、これらの型を組み込み整数(レジスタ機能と最適化を含む)とまったく同じように扱います。

しかし、このトリックは(悲しいことに)浮動小数点数には使用できません。最も良い必要性は、明らかに実数値の次元チェックです。リンゴとナシを混同しないでください。長さと面積を追加することは一般的なエラーです。

@JerryによるStroustrupの呼び出しは関係ありません。仮想性は主に公的継承にとって意味があり、私的継承に向けての必要性がここにあります。基本タイプの「カオス的」C変換ルール(C ++ 14にはカオス的でないものはありますか?)に関する考慮事項も役に立ちません。目的は、標準の変換ルールに従わず、デフォルトの変換ルールを持たないことです。

于 2015-08-18T14:24:48.287 に答える
-1

私が覚えているなら、これがC ++が真のオブジェクト指向言語と見なされなかった主な理由(またはその1つ)でした。ジャワの人々はこう言うでしょう:「ジャワでは、すべてがオブジェクトです」;)

于 2010-01-26T22:27:04.400 に答える
-3

これは、アイテムがメモリに保存される方法に関連しています。C ++のintは、他の場所で説明されているように整数型であり、メモリ内では32ビットまたは64ビット(ワード)です。ただし、オブジェクトはメモリに別の方法で格納されます。通常はヒープに格納され、ポリモーフィズムに関連する機能があります。

私はそれをもっとよく説明する方法がわかりません。4番からどのように受け継いでいますか?

于 2010-01-26T22:56:33.943 に答える
-4

継承したいのに、なぜintから継承できないのですか?

パフォーマンス

int、char、char *などの序数型から(任意の言語で)継承できないようにする必要がある機能的な理由はありません。JavaやObjective-Cなどの一部の言語は、実際にクラス/オブジェクトを提供します(ボックス化)このニーズを満たすために(および、オブジェクトではない序数型のその他の不快な結果に対処するために)、基本型のバージョン:

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

しかし、Javaとobjective-cでさえ、使用するために序数型を保持しています...なぜですか?

単純な理由は、パフォーマンスとメモリ消費です。序数型は通常、1つまたは2つのX86命令で構築、操作、および値の受け渡しが可能であり、最悪の場合、数バイトしか消費しません。クラスは通常できません。多くの場合、2倍以上のメモリを使用し、その値の操作には数百サイクルかかる場合があります。

つまり、これを理解しているプログラマーは、通常、序数型を使用してパフォーマンスやメモリ使用量に敏感なコードを実装し、言語開発者が基本型をサポートすることを要求します。

かなりの数の言語が序数型を持たないことに注意してください。特に、などの動的言語はperl、ほぼ完全に可変個引数型に依存しており、クラスのオーバーヘッドの一部を共有しています。

于 2010-01-27T00:01:40.750 に答える