web-dev-qa-db-ja.com

C ++でintから継承できないのはなぜですか?

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

class myInt : public int
{

};

なぜ私はできないのですか?

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

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

「Intにはメンバーデータがありません。」はい、あります。32ビットなどです。

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

61
Rocketmagnet

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

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

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

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

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

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

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

それはnotですが、どのようにsmallIntegerが実装されていますか?その場合、通常はポインタとなるものに直接整数値を入れます。 smallIntegerとポインタを区別するために、それらはすべてのオブジェクトを偶数バイト境界に割り当てることを強制するため、LSBは常に明確です。 smallIntegerには常にLSBが設定されています。

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


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

82
Jerry Coffin

Intはクラスではなく序数型です。なぜあなたはしたいですか?

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

更新

@OP "Int is not's Classes" so?

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

Int、chars、およびその他の序数型にはmethod tablesがないため、メソッドを追加したりオーバーライドしたりすることはできません。これが、継承の中心です。

53
3Dave

なぜ私はしたいですか?より強力なタイピング。たとえば、2つのクラス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からの継承が合法であると偽って、SpecialIntintから派生した場合、SpecialInt + intwouldコンパイルします。継承すると、避けたい正確な問題が発生します。 Not継承することで問題を簡単に回避できます。

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

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

25
jalf

OPがWHY C++が現状のままであることを本当に理解したい場合は、Stroustupの著書「The Design and Evolution of C++」のコピーを入手する必要があります。これは、C++の初期の頃における、これと他の多くの設計上の決定の根拠を説明しています。

14

Intはクラスではなくネイティブ型であるため

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

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

別の方法で考えてみてください。 int/float/charには、データメンバーやメソッドはありません。プリミティブをクォークと考えてください-それらは細分化できない構成要素であり、それらを使用してより大きなものを作成します(私の類推が少しずれている場合は謝罪、十分な素粒子物理学を知らない)

10
Mason

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

次に、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) {}
}

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

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

9
Alex Brown

他の人が言ったことは本当です... intはC++のプリミティブです(C#によく似ています)。ただし、intの周りにクラスを構築するだけで、希望どおりの結果を得ることができます。

class MyInt
{
private:
   int mInt;

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

それから、あなたがおもしろいことすべてを継承することができます。

5
Polaris878

C++がCとの後方互換性を(主に)持つように設計されており、Cコーダーのアップグレードパスを容易にするためにstructをデフォルトですべてのメンバーpublicに設定するなど、だれも言及していません。

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

4
zebrabox

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

3
Trent

他の人が言っているように、intはプリミティブ型なので実行できません。

ただし、よりタイピングが強いのであれば、その動機は理解できます。 特別な種類のtypedef で十分であることがC++ 0xでも提案されています(ただし、これは拒否されましたか?)。

自分でベースラッパーを提供した場合、おそらく何かが達成される可能性があります。たとえば、次のようなものは、好奇心を持って定期的に繰り返されるテンプレートを合法的に使用し、クラスの派生と適切なコンストラクタの提供のみを必要とします。

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
UncleBens

まあ、仮想メンバー関数を持っていないものを継承する必要は本当にありません。したがって、intあったクラスであっても、合成にプラス記号はありません。

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

2
Debilski

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

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

1
anon

強力なtypedefで必要なものを取得できます。 BOOST_STRONG_TYPEDEF を参照

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

0
Chuck

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

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

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

そして提案された拡張

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

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

これは、ささいな演算を最適化するためにコンパイラに非常に大きな負担をかけます。

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

コンパイラーのインプリメンターはタイプチェックコードを導入します(おそらくいくつかのエラーがありますが、コンパイラーのバグにより無数のエラーが発生するため、コンパイラーの正確さはクライアントコードよりもはるかに優れています)。ただし、操作の主な動作はまったく同じで、エラーはほとんどありません。いつもより。

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

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

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

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

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

@JerryによるStroustrupの呼び出しは無関係です。仮想性は主に公共の継承に意味があり、ここで必要なのは私的な継承です。基本型の「無秩序な」C変換規則(C++ 14には無秩序なものはありますか?)に関する考慮も役に立ちません。デフォルトの変換規則を持たず、標準の規則に従わないことが目的です。

0
alta

この回答は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
over_optimistic