C ++ですべてのオブジェクトに対応するベースが推奨されない理由


76

Stroustrup氏は、「すべてのクラス(Objectクラス)に一意のベースをすぐに発明しないでください。通常、多くの/ほとんどのクラスでそれを使用せずに改善できます。」(C ++プログラミング言語Fourth Edition、Sec 1.3.4)

一般に、すべてを対象とする基本クラスが悪い考えであるのはなぜですか?また、いつ作成するのが理にかなっていますか?


16
C ++はJavaではないからです...そして、それを強制することを試みるべきではありません。
AK_

10
スタックオーバーフローに関する質問:C ++に基本クラスがないのはなぜですか?

26
また、私は「主に意見に基づいた」ための投票に反対します。この質問とリンクされたSO質問の両方について回答が証明されているため、これについて説明できる非常に具体的な理由があります。

2
アジャイルの原則は「必要ない」という原則です。既に特定の必要性を特定していない限り、(あなたがするまで)それをしないでください。
Jool

3
@AK_:コメントに「とんでもない」がありません。
-DeadMG

回答:


75

そのオブジェクトは機能のために何を持っているのですか?Javaでは、Baseクラスにあるのは、toString、hashCodeと等式、およびmonitor + condition変数のみです。

  • ToStringはデバッグにのみ役立ちます。

  • hashCodeは、ハッシュベースのコレクションに格納する場合にのみ役立ちます(C ++の優先事項は、ハッシュ関数をテンプレートパラメーターとしてコンテナに渡すか、std::unordered_*完全に回避し、代わりにstd::vectorプレーンな順序なしリストを使用することです)。

  • コンパイル時にベースオブジェクトのない等式が役立ちます。同じ型を持たない場合、等号にすることはできません。C ++では、これはコンパイル時エラーです。

  • モニターおよび条件変数は、ケースバイケースで明示的に含める方が適切です。

ただし、必要なことがさらにある場合は、ユースケースがあります。

たとえば、QTには、QObjectスレッドアフィニティ、親子所有権階層、およびシグナルスロットメカニズムの基礎を形成するルートクラスがあります。また、QObjectのポインターによる使用を強制しますが、Qtの多くのクラスはシグナルスロット(特に一部の説明の値型)を必要としないため、QObjectを継承しません


7
おそらくJavaに基本クラスがある主な理由を言及するのを忘れていました:ジェネリックの前に、コレクションクラスは機能するために基本クラスを必要としていました。すべて(内部ストレージ、パラメーター、戻り値)が入力されましたObject
アレクサンドルドゥビンスキー

1
@AleksandrDubinsky:そしてジェネリックは構文糖を追加しただけで、実際には何も変えず、洗練されています。
デュプリケータ

4
私は、ハッシュコード、同等性、およびモニターのサポートもJavaの設計ミスであると主張します。すべてのオブジェクトをロックするのは良い考えだと誰が思ったのですか?!
usr

1
ええ、しかし誰もそれを望んでいません。オブジェクトをロックする必要があり、別のロックオブジェクトをインスタンス化できなかったのはいつですか。それは非常にまれであり、すべてに負担をかけます。Javaの人たちは、すべてのオブジェクトがロックされていることと、現在では非推奨となっているスレッドセーフコレクションの証拠として、スレッドセーフについてよく理解していなかった。スレッドセーフは、オブジェクトごとのプロパティではなく、グローバルプロパティです。
usr

2
あなたは、ハッシュベースのコレクションにそれを保存したい場合のhashCodeは(C ++での好みはのstd ::ベクトルとプレーン順不同リスト用)のみ有効です。」と本当の反論は、_hashCode「別のコンテナを使用する」のではなく指していませんstd::unordered_map実装を提供するために要素クラス自体を必要とする代わりに、C ++ がテンプレート引数を使用してハッシュを行うことを確認してください。つまり、C ++の他のすべての優れたコンテナーおよびリソースマネージャーと同様に、侵入的ではありません。誰かが後で何らかのコンテキストでそれらを必要とする可能ある場合に備えて、すべてのオブジェクトを関数またはデータで汚染しません。
underscore_d

100

すべてのオブジェクトで共有される機能がないためです。このインターフェイスには、すべてのクラスにとって意味のあることは何もありません。


10
答えを簡単にするために+1が必要です。これが本当に唯一の理由です。
BWG

7
私が経験した大規模なフレームワークでは、共通の基本クラスが、<whatever>コンテキストで必要なシリアル化およびリフレクションインフラストラクチャを提供します。えー その結果、データとメタデータと共に大量のデータをシリアル化し、効率的ではないほどデータ形式が大きく複雑になりました。
dmckee

19
@dmckee:また、シリアル化とリフレクションが普遍的に有用なニーズとはほとんど言えないと主張します。
-DeadMG

16
@DeadMG:「しかし、すべてを保存する必要がある場合はどうなりますか?」
-deworde

8
私は知りません、あなたはそれを引用符で囲み、あなたはすべてのキャップを使います、そして人々は冗談を見ることができません。@MSalters:それは簡単なことです。最小限の状態しかありません。指定するだけです。再帰ループに入ることなく、リストに自分の名前を書くことができます。
-deworde

25

オブジェクトの高い継承階層を構築するたびに、壊れやすい基本クラス(Wikipedia)の問題に遭遇する傾向があります。

多くの小さな個別の(別個の、分離された)継承階層があると、この問題に遭遇する可能性が低くなります。

すべてのオブジェクトを1つの巨大な継承階層の一部にすることで、実際にこの問題に遭遇することが保証されます。


6
基本クラス(Java "java.lang.Object")に他のメソッドを呼び出すメソッドが含まれていない場合、壊れやすい基本クラスの問題は発生しません。
マーティンローゼナウ

3
強力な便利な基本クラスです!
マイクナキス

9
@MartinRosenau ...マスターベースクラスを必要とせずにC ++でできるように!
gbjbaanb

5
@DavorŽdraloですから、C ++には基本関数(「DebugPrint」のような賢明なものではなく「演算子<<」)の愚かな名前があります。私はC ++のいぼがもっと好きだと思います。
セバスチャンレッド

4
@DavorŽdralo:関数の名前は無関係です。構文をイメージしてくださいcout.print(x).print(0.5).print("Bye\n")-それは依存しませんoperator<<
MSalters

24

なぜなら:

  1. 使用しないものに料金を支払うべきではありません。
  2. これらの関数は、値ベースの型システムでは、参照ベースの型システムよりも意味がありません。

あらゆる種類のvirtual機能を実装すると、仮想テーブルが導入されますが、仮想テーブルでは、オブジェクトごとのスペースオーバーヘッドが必要になります。

toString非仮想的に実装することは、Javaの場合とは異なり、非常にユーザーフレンドリーであり、呼び出し元が既にアクセスしているオブジェクトのアドレスのみを返すため、ほとんど役に立たないでしょう。
同様に、非仮想equalsまたはhashCodeアドレスのみを使用してオブジェクトを比較できますが、これはかなり役に立たず、しばしば完全に間違っています-Javaとは異なり、オブジェクトはC ++で頻繁にコピーされるため、オブジェクトの「同一性」を区別することさえできません常に有意義または有用です。(たとえば、int実際にはその値以外の同一性を持たてはいけません。同じ値の2つの整数は等しくなければなりません。)


この問題とMike Nakisが指摘した脆弱な基本クラスの問題に関連して、基本的にすべてのメソッドを内部(つまり、同じクラスから呼び出された場合)を非仮想にすることでJavaで修正する興味深い研究/提案に注意してください。外部から呼び出されます。古い/標準の動作(つまり、どこでも仮想)を取得するために、提案では新しいキーワードが導入されました。私はそれがいくつかの論文を超えてどこにも行ったとは思わない open
フィズ

その論文に関するもう少しの議論はlambda-the-ultimate.org/classic/message12271.html
フィズ

共通の基底クラスを持つことは、それが可能テストすることになるだろういずれかを shared_ptr<Foo>それでもあるかどうかを確認するためにshared_ptr<Bar>(他のポインタ型または同様に)しても、FooそしてBarお互いについて何も知らない無関係なクラスがあります。そのようなものが「生のポインタ」で動作することを要求することは、そのようなものがどのように使用されるかの歴史を考えると、高価になりますが、とにかくヒープに保存されるものについては、追加コストは最小限になります。
supercat

すべてに共通の基本クラスを持つことは役に立たないかもしれませんが、共通の基本クラスが役立つと思われるオブジェクトのかなり大きなカテゴリがあると思います。たとえば、Javaの多くのクラス(多数ではないが実質的に複数)は、2つの方法で使用できます:可変データの非共有保有者として、または誰も変更できないデータの共有可能保有者として。どちらの使用パターンでも、マネージポインター(参照)は、基になるデータのプロキシとして使用されます。このようなすべてのデータに共通のマネージポインタータイプを使用できると便利です。
supercat

16

1つのルートオブジェクトがあると、多くの見返りなしに、実行できることとコンパイラーが実行できることを制限します。

共通のルートクラスを使用すると、任意のコンテナーを作成し、dynamic_castでその内容を抽出することがboost::anyできますが、任意のコンテナーが必要な場合は、共通のルートクラスがなく同様のことができます。そしてboost::anyまた、プリミティブをサポートしています-それは小さくても、バッファの最適化をサポートし、Javaの用語ではほとんど「箱なし」、それらを残すことができます。

C ++は、値型をサポートし、成功しています。リテラル、およびプログラマーが作成した値型。C ++コンテナは、値型を効率的に保存、ソート、ハッシュ、消費、生成します。

継承、特にモノリシックな継承Javaスタイルの基本クラスの種類は、フリーストアベースの「ポインター」または「参照」型を必要とします。データへのハンドル/ポインター/参照は、クラスのインターフェイスへのポインターを保持し、多態的に他の何かを表すことができます。

これはいくつかの状況で役立ちますが、「共通の基本クラス」を使用してパターンと結婚すると、コードパターン全体が有用でなくても、このパターンのコストと手荷物に縛られることになります。

ほとんどの場合、呼び出し側のサイトまたはそれを使用するコードのいずれかで、「オブジェクトである」というよりも、型について多くのことを知っています。

関数が単純な場合、関数をテンプレートとして記述すると、呼び出し側の情報が破棄されない、アヒル型のコンパイル時間ベースのポリモーフィズムが得られます。関数がより複雑な場合は、型の消去を実行して、実行する型の統一操作(たとえば、シリアル化と逆シリアル化)を構築し、保存して(コンパイル時に)消費する(実行時に)ことができます。別の翻訳単位のコード。

すべてをシリアル化できるようにするライブラリがあるとします。1つのアプローチは、基本クラスを持つことです。

struct serialization_friendly {
  virtual void write_to( my_buffer* ) const = 0;
  virtual void read_from( my_buffer const* ) = 0;
  virtual ~serialization_friendly() {}
};

今、あなたが書くコードのすべてのビットが可能ですserialization_friendly

void serialize( my_buffer* b, serialization_friendly const* x ) {
  if (x) x->write_to(b);
}

を除いてstd::vector、すべてのコンテナを記述する必要があります。そして、そのbignumライブラリから取得した整数ではありません。そして、あなたが書いたタイプではなく、シリアル化が必要だとは思わなかった。そしてないtuple、またはintあるいはdouble、またはstd::ptrdiff_t

別のアプローチを取ります。

void write_to( my_buffer* b, int x ) {
  b->write_integer(x);
}    
template<class T,
  class=std::enable_if_t< void_t<
    std::declval<T const*>()->write_to( std::declval<my_buffer*>()
  > >
>
void write_to( my_buffer* b, T const* x ) {
  if (x) x->write_to(b);
}
template<class T>
void serialize( my_buffer* b, T const& t ) {
  write_to( b, t );
}

一見、何もしないことで構成されています。これを除き、型の名前空間内の自由な関数または型のメソッドとしてwrite_toオーバーライドすることで拡張できますwrite_to

型の消去コードを少し書くこともできます:

namespace details {
  struct can_serialize_pimpl {
    virtual void write_to( my_buffer* ) const = 0;
    virtual void read_from( my_buffer const* ) = 0;
    virtual ~can_serialize_pimpl() {}
  };
}
struct can_serialize {
  void write_to( my_buffer* b ) const { pImpl->write_to(b); }
  void read_from( my_buffer const* b ) { pImpl->read_from(b); }
  std::unique_ptr<details::can_serialize_pimpl> pImpl;
  template<class T> can_serialize(T&&);
};
namespace details { 
  template<class T>
  struct can_serialize : can_serialize_pimpl {
    std::decay_t<T>* t;
    void write_to( my_buffer*b ) const final override {
      serialize( b, std::forward<T>(*t) );
    }
    void read_from( my_buffer const* ) final override {
      deserialize( b, std::forward<T>(*t) );
    }
    can_serialize(T&& in):t(&in) {}
  };
}
template<class T> can_serialize::can_serialize<T>(T&&t):pImpl(
  std::make_unique<details::can_serialize<T>>( std::forward<T>(t) );
) {}

そして今、私たちは任意の型を取り、can_serializeインターフェースに自動ボックス化してserialize、後で仮想インターフェースを通して呼び出すことができるようにします。

そう:

void writer_thingy( can_serialize s );

の代わりに、シリアル化できるものをすべて取る関数です

void writer_thingy( serialization_friendly const* s );

1つ目は2つ目とは異なりint、をstd::vector<std::vector<Bob>>自動的に処理できます。

特にこのようなことはめったにしたくないので、それを書くのに多くはかかりませんでしたが、ベース型を必要とせずにシリアル化できるものとして扱うことができるようになりました。

さらに、std::vector<T>単にオーバーライドwrite_to( my_buffer*, std::vector<T> const& )することで、ファーストクラスの市民としてシリアライズ可能にすることができます-そのオーバーロードで、それをvtableに格納しcan_serialize、のシリアライズstd::vector可能性をvtableに保存してアクセスできます.write_to

要するに、C ++は十分に強力であるため、必要なときに強制継承階層の価格を支払うことなく、必要に応じてオンザフライで単一の基本クラスの利点を実装できます。また、単一のベース(偽造されているかどうかに関係なく)が必要になるのは、かなりまれです。

タイプが実際にそれらのアイデンティティであり、あなたがそれらが何であるかを知っているとき、最適化の機会はたくさんあります。データはローカルに連続して保存されます(これは現代のプロセッサでのキャッシュの使いやすさにとって非常に重要です)。反対側)命令を最適に並べ替えることができ、丸穴に打たれる丸釘が少なくなります。


8

上記には多くの良い答えがありますが、@ ratchetfreakの答えとそのコメントに示されているように、すべてのオブジェクトの基本クラスで行うことは他の方法でより良くできるという明確な事実は非常に重要ですが、別の理由があります。それは、継承ダイヤモンドの作成を避けることです多重継承が使用される場合。ユニバーサル基本クラスに何らかの機能がある場合、多重継承の使用を開始するとすぐに、継承チェーンの異なるパスで異なるオーバーロードが発生する可能性があるため、アクセスするバリアントの指定を開始する必要があります。また、ベースを仮想化することはできません。これは非常に非効率的であるためです(すべてのオブジェクトが、メモリ使用量とローカリティの潜在的に莫大なコストで仮想テーブルを持つ必要があります)。これは、すぐに物流上の悪夢になります。


1
ダイアモンドの問題に対する1つの解決策は、複数のパスを介して非仮想的にベースタイプを派生するすべてのタイプで、そのベースタイプのすべての仮想メンバーをオーバーライドすることです。共通の基本型が最初から言語に組み込まれている場合、コンパイラは正当な(必ずしも印象的ではないが)デフォルトの実装を自動生成できます。
supercat

5

実際、Microsoftの初期のC ++コンパイラとライブラリ(16ビットのVisual C ++について知っている)には、という名前のクラスがありましたCObject

ただし、この時点では「テンプレート」はこの単純なC ++コンパイラではサポートされていなかったため、次のようなクラスstd::vector<class T>は不可能だったことを知っておく必要があります。代わりに、「ベクター」実装では1つのタイプのクラスしか処理できないため、std::vector<CObject>今日に匹敵するクラスがありました。CObjectほぼすべてのクラスの基本クラスであったため(残念ながら、最新のコンパイラのCString同等でstringはありません)、ほぼすべての種類のオブジェクトを格納するためにこのクラスを使用できます。

最新のコンパイラはテンプレートをサポートしているため、この「汎用ベースクラス」の使用例は提供されなくなりました。

このようなジェネリック基本クラスを使用すると、メモリとランタイムが(少しだけ)コストがかかるという事実を考慮する必要があります(たとえば、コンストラクターの呼び出しなど)。そのため、このようなクラスを使用する場合には欠点がありますが、少なくとも最新のC ++コンパイラを使用する場合には、そのようなクラスのユースケースはほとんどありません。


3
そのMFCですか?[コメントのパディング]
-user253751

3
それはまさにMFCです。物事がどのように行われるべきかを世界に示したオブジェクト指向デザインの輝くビーコン。ああ、待って
...-gbjbaanb

4
@gbjbaanb Turbo PascalとTurbo C ++には、TObjectMFCが存在する前から独自の機能がありました。設計のその部分をマイクロソフトのせいにしないでください。その頃のほとんどの人にとって、それは良い考えのように思えました。
hvd

テンプレートの前でさえ、C ++でSmalltalkを記述しようとすると、ひどい結果が生じました。
JDługosz

@hvdそれでも、MFCはBorlandが作成したどの製品よりもはるかに悪いオブジェクト指向設計の例でした。
ジュール

5

Javaに由来する別の理由を提案します。

少なくともボイラープレートがなければ、すべての基本クラスを作成できないからです

あなたはあなた自身のクラスのためにそれで逃げることができるかもしれません-しかし、あなたはおそらくあなたが多くのコードを複製することになってしまうでしょう。たとえば、「std::vector実装されていないため、ここでは使用できません。正しいことを行うIObject新しい派生IVectorObjectを作成した方がよいでしょう...」。

これは、組み込みまたは標準ライブラリクラスまたは他のライブラリのクラスを扱う場合に当てはまります。

今、それはあなたがのようなもので終わるだろう言語に組み込まれた場合、IntegerおよびintJavaであり、混乱、または言語の構文に大きな変化。(他の言語はすべてのタイプに組み込むことで素晴らしい仕事をしたと思います-ルビーはより良い例のようです。)

また、基本クラスがランタイムポリモーフィックでない場合(つまり、仮想関数を使用している場合)、フレームワークなどの特性を使用することでも同じメリットが得られることに注意してください。

たとえば.toString()、次のようにすることができます:(注:既存のライブラリなどを使用してこの整頓を行うことができることを知っています、それは単なる例です。)

template<typename T>
struct ToStringTrait;

template<typename T> 
std::string toString(const T & t) {
  return ToStringTrait<T>::toString(t);
}

template<>
struct ToStringTrait<int> {
  std::string toString(int v) {
    return itoa(v);
  }
}

template<typename T>
struct ToStringTrait<std::vector<T>> {
  std::string toString(const std::vector<T> &v) {
    std::stringstream ss;
    ss<<"{";
    for(int i=0; i<v.size(); ++i) {
      ss<<toString(v[i]);
    }
    ss<<"}";
    return ss.str();
  }
}

3

おそらく「void」は、ユニバーサルベースクラスの多くの役割を果たします。任意のポインターをにキャストできますvoid*。その後、これらのポインターを比較できます。static_cast元のクラスに戻ることができます。

しかし、あなたがすることができないでくださいvoidあなたが行うことができますことはObject、あなたが本当に持っているオブジェクトの種類を把握するためにRTTIを使用しています。これは最終的に、C ++のすべてのオブジェクトがRTTIを持っているわけではなく、実際に幅ゼロのオブジェクトを持つことも可能です。


1
通常のサブオブジェクトではなく、幅がゼロのベースクラスサブオブジェクトのみ。
デデュプリケーター

@Deduplicator更新により、C ++ 17はを追加します[[no_unique_address]]。これは、コンパイラがメンバーサブオブジェクトにゼロ幅を与えるために使用できます。
underscore_d

1
@underscore_d C ++ 20を計画しているという[[no_unique_address]]ことは、コンパイラがEBOメンバー変数を許可するということです。
デュプリケータ

@Deduplicatorおっと、うん。私はすでにC ++ 17の使用を開始しましたが、実際よりも最先端であると思います。
underscore_d

2

Javaは、「未定義の動作」が存在してはならないという設計哲学を採用しています。次のようなコード:

Cat felix = GetCat();
Woofer Rover = (Woofer)felix;
Rover.woof();

interfaceを実装felixするサブタイプを保持しているかどうかをテストしCatますWoofer。実行されているwoof()場合、キャストを実行して呼び出し、実行されていない場合は例外をスローします。 コードの動作は、felix実装するかどうWooferかに関係なく完全に定義されます。

C ++は、プログラムが何らかの操作を試みてはならない場合、その操作が試みられた場合に生成されたコードが何をするかは問題ではなく、コンピューターは「あるべき」場合の動作を制限しようとして時間を無駄にしないという哲学を採用しています決して起こらない。C ++では、a *Catをa にキャストするために適切な間接演算子を追加すると*Woofer、キャストは正当な場合に定義された動作を生成しますが、そうでない場合には未定義の動作を生成します。

物事に共通の基本型を持たせることで、その基本型の派生物の間でキャストを検証したり、キャスト試行操作を実行したりすることができますが、キャストの検証は、それらが正当であり、悪いことが起こらないと単純に想定するよりもコストがかかります。C ++の哲学では、そのような検証には「(通常は)必要のないものに対する支払い」が必要であるというものです。

C ++に関連するが、新しい言語の問題ではない別の問題は、複数のプログラマがそれぞれ共通のベースを作成し、それから独自のクラスを派生し、その共通のベースクラスのことを処理するコードを記述する場合、そのようなコードは、異なる基本クラスを使用したプログラマーによって開発されたオブジェクトを使用できません。新しい言語ですべてのヒープオブジェクトに共通のヘッダー形式が必要であり、許可されていないヒープオブジェクトを許可したことがない場合、そのようなヘッダーを持つヒープオブジェクトへの参照を必要とするメソッドは、任意のヒープオブジェクトへの参照を受け入れます作成することができます。

個人的には、オブジェクトに「タイプXに変換可能か」と尋ねる一般的な手段があることは、言語/フレームワークの非常に重要な機能であると思いますが、そのような機能が最初から言語に組み込まれていない場合、それは困難です後で追加します。個人的には、このような基本クラスは最初に標準ライブラリに追加する必要があり、多態的に使用されるすべてのオブジェクトはその基本から継承することを強く推奨します。プログラマーがそれぞれ独自の「基本型」を実装すると、異なる人々のコード間でオブジェクトを渡すのが難しくなりますが、多くのプログラマーが継承した共通の基本型を使用すると簡単になります。

補遺

テンプレートを使用して、「任意のオブジェクトホルダー」を定義し、そこに含まれるオブジェクトのタイプについて尋ねることができます。Boostパッケージには、そのようなものが含まれていanyます。したがって、C ++には標準の「何かに対する型チェック可能な参照」型はありませんが、作成することは可能です。これは、言語標準に何かがないという前述の問題、つまり異なるプログラマーの実装間の非互換性を解決するものではありませんが、すべてが派生するベース型がなくてもC ++がどのように到達するかを説明します。何かのように振る舞います。


そのキャストは、C ++Java、およびC#でのコンパイル時に失敗します。
ミレニアムバグ

1
@milleniumbug:WooferがインターフェースでCatあり、継承可能な場合、キャストはWoofingCatから継承Catおよび実装するが存在する可能性があるため(現在ではない場合、将来的に)正当であると考えられますWoofer。Javaのコンパイル/リンクモデルでは、aの作成WoofingCatにはのソースコードへのアクセスは必要ありCatませんWoofer
supercat

3
C ++にはdynamic_castがあり、これはaからa Catへのキャストの試行を適切に処理し、Woofer「X型に変換可能ですか」という質問に答えます。C ++を使用すると、キャストを強制することができます。ちょっとしたことがあるかもしれません。実際に何をしているのかを実際に知っているかもしれませんが、それがあなたが本当にやりたいことではない場合にも役立ちます。
ロブK

2
@RobK:もちろん、構文については正しいです。ミー・カルパ。私はdynamic_castについてもう少し読んでいますが、ある意味では、現代のC ++はすべてのポリモーフィックオブジェクトがオブジェクトのタイプ(通常はvtable)を識別するために必要なフィールドを持つベース「ポリモーフィックオブジェクト」ベースクラスから派生しているようですポインター、それは実装の詳細です)。C ++は多態性クラスをそのように記述しませんがdynamic_cast、多態性オブジェクトを指す場合はポインタを渡すことで定義された動作を、そうでない場合は未定義の動作を定義します。そのため、セマンティックの観点から...
supercat

2
...すべてのポリモーフィックオブジェクトは同じレイアウトで情報を保存し、すべては非ポリモーフィックオブジェクトではサポートされない動作をサポートします。私の考えでは、それは、言語定義がそのような用語を使用しているかどうかに関係なく、共通のベースから派生したかのように振る舞うことを意味します。
スーパーキャット

1

実際、Symbian C ++には、特定の方法で動作するすべてのオブジェクト(主にヒープを割り当てた場合)のユニバーサルベースクラスであるCBaseがありました。仮想デストラクタを提供し、構築時にクラスのメモリをゼロにし、コピーコンストラクタを隠しました。

背後にある理論的根拠は、組み込みシステム用の言語であり、C ++コンパイラーと仕様が10年前に本当にくそだったということです。

すべてのクラスがこれから継承されているわけではなく、一部のクラスのみです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.