5

C++ の専門家であり、D 言語の作成者であるWalter Brightは次のように述べています。

スライシングの問題は、メモリの破損につながる可能性があるため深刻であり、プログラムがスライシングに悩まされていないことを保証することは非常に困難です。言語外で設計するには、継承をサポートするクラスは参照のみでアクセスできるようにする必要があります (値ではアクセスできません)。D プログラミング言語には、このプロパティがあります。

オブジェクトのスライスの問題がメモリの破損を引き起こす C++ の例を示して、誰かが説明した方がよいでしょうか? そして、この問題はD言語によってどのように解決されるのでしょうか?

4

3 に答える 3

3

適切にモデル化するのが難しい継承の側面は、次のように言うと便利な場合があることです。

  1. ATは、タイプ の変数に代入可能でなければなりませんU
  2. *Tは に割り当て可能である必要があります*U
  3. const *Tは に割り当て可能である必要がありますconst *U

しかし、C++ はそれらを区別しません。Java と C# は、2 番目のセマンティクスのみを提供することで問題を回避します (クラス オブジェクト インスタンスを保持する変数を持つことはできません。これらの言語はポインター表記を使用しませんが、すべてのクラス型変数は、別の場所に格納されたオブジェクトへの暗黙的な参照です)。Uただし、C++ では、最初の形式なしで 2 番目または 3 番目の形式を単純に許可する単純な形式の宣言はありません。また、「型の変数に格納できる何かへのポインター」と「何かへのポインター」を区別する方法もありません。これには、" のすべての仮想メンバーと非仮想メンバーが含まれますU。言語の型システムが「厳密な」ポインター型と「非厳密な」ポインター型を区別することは可能です。U

  1. type の変数に格納できない任意の型でオーバーライドする必要がありUます。

  2. メソッド内ではthis、型は asU strict *である必要があり、型の変数を逆参照すると、型の右辺値が割り当てられない場合でも、型のいずれかに割り当て可能であるU strict *型の右辺値が生成される必要があります。U strictUU

Uただし、C++ にはそのような区別はありません。つまり、 type の変数に格納できる何かへのポインターを必要とするメソッドと、同じメンバーを持つ何かを必要とするメソッドを区別する方法がないことを意味します。

于 2015-09-07T17:39:50.027 に答える
2

次の単純な小さな C++ プログラムとその出力は、スライスの問題と、それがメモリの破損につながる理由を示しています。

D、Java、C# などの言語では、参照ハンドルを介して変数にアクセスします。これは、変数に関するすべての情報が参照ハンドルに関連付けられていることを意味します。C++ では、変数に関する情報は、コンパイルが行われているときのコンパイラの状態の一部です。C++ ランタイム タイプ情報 (RTTI) を有効にすると、実行時にオブジェクト タイプを確認するメカニズムが提供されますが、スライシングの問題にはあまり役立ちません。

基本的に、C++ は速度を上げるためにセーフティ ネットを取り除きます。

C++ コンパイラには、クラスで特定のメソッド (コピー コンストラクターや代入演算子など) が提供されていない場合に、コンパイラーが独自の既定のバージョンを作成するために最善を尽くすために使用する一連の規則があります。コンパイラには、特定のメソッドが利用できない場合に、ソースステートメントの意味を表現するコードを作成する別の方法を探すために使用する規則もあります。

コンパイラが役に立ちすぎて、結果が危険になる場合があります。

この例でlevelOneは、基本クラスとlevelTwo派生クラスの 2 つのクラスがあります。基本クラスのオブジェクトへのポインターがオブジェクトの派生クラス部分もクリーンアップするように、仮想デストラクタを使用します。

出力では、派生クラスを基本クラスに割り当てるとスライスが発生し、デストラクタが呼び出されると、基本クラスのデストラクタのみが呼び出され、派生クラスのデストラクタは呼び出されないことがわかります。

派生クラスのデストラクタが呼び出されないという結果は、派生オブジェクトが所有するリソースが適切に解放されない可能性があることを意味します。

これが簡単なプログラムです。

#include "stdafx.h"
#include <iostream>

class levelOne
{
public:
    levelOne(int i = 1) : iLevel(i) { iMyId = iId++; std::cout << "  levelOne construct  " << iMyId << std::endl; }
    virtual ~levelOne() { std::cout << "  levelOne destruct  " << iMyId << "  iLevel = " << iLevel << std::endl; }

    int  iLevel;
    int  iMyId;

    static int iId;
};

int levelOne::iId = 1;

class levelTwo : public levelOne
{
public:
    levelTwo(int i = 2) : levelOne(i) { jLevel = 2; iMyTwoId = iTwoId++;  std::cout << "     levelTwo construct  " << iMyId << ", " << iMyTwoId << std::endl; }
    virtual ~levelTwo() { std::cout << "     levelTwo destruct  " << iMyId << ", " << iMyTwoId << "  iLevel = " << iLevel << "  jLevel = " << jLevel << std::endl; }

    int  jLevel;
    int  iMyTwoId;

    static int iTwoId;
};

int levelTwo::iTwoId = 101;


int _tmain(int argc, _TCHAR* argv[])
{
    levelOne one;
    levelTwo two;

    std::cout << "Create LevelOne and assign to it a LevelTwo" << std::endl;
    levelOne aa;     // create a levelOne object
    aa = two;        // assign to the levelOne object a levelTwo object

    std::cout << "Create LevelTwo and assign to it a LevelOne pointer then delete it" << std::endl;
    levelOne *pOne = new levelTwo;
    delete pOne;

    std::cout << "Exit program." << std::endl;
    return 0;
}

出力はpOne = new levelTwo;、ID が で作成されたオブジェクトがとデストラクタの4両方にヒットし、オブジェクトの破壊を適切に処理していることを示しています。levelTwolevelOne

ただし、オブジェクトのデストラクタが呼び出されると、オブジェクトのデストラクタのみが実行されるように、メモリ コピーのみを行うデフォルトの代入演算子が使用されるため、levelTwoオブジェクトtwoへのlevelOneオブジェクトの割り当てはスライスになります。派生クラスは解放されません。aaaalevelOne

次に、他の 2 つのオブジェクトは、プログラムの終了時にすべてスコープ外になるため、適切に破棄されます。このログを読むと、デストラクタが構築の逆の順序で呼び出されることを思い出してください。

  levelOne construct  1
  levelOne construct  2
     levelTwo construct  2, 101
Create LevelOne and assign to it a LevelTwo
  levelOne construct  3
Create LevelTwo and assign to it a LevelOne pointer then delete it
  levelOne construct  4
     levelTwo construct  4, 102
     levelTwo destruct  4, 102  iLevel = 2  jLevel = 2
  levelOne destruct  4  iLevel = 2
Exit program.
  levelOne destruct  2  iLevel = 2
     levelTwo destruct  2, 101  iLevel = 2  jLevel = 2
  levelOne destruct  2  iLevel = 2
  levelOne destruct  1  iLevel = 1
于 2015-09-07T16:45:23.357 に答える