36

C++03 標準の の下に次のスニペットが見つかりました5.3.5 [expr.delete] p3

最初の選択肢 (オブジェクトの削除) では、削除するオブジェクトの静的型がその動的型と異なる場合、静的型はオペランドの動的型の基本クラスであり、静的型は仮想デストラクタまたは動作は未定義です。2 番目の選択肢 ( delete array ) では、削除するオブジェクトの動的な型がその静的な型と異なる場合、動作は未定義です。


静的型と動的型のクイック レビュー:

struct B{ virtual ~B(){} };
struct D : B{};

B* p = new D();

の静的タイプはpですがB*、動的タイプ*pD,1.3.7 [defns.dynamic.type]です。

[:p静的型が「へのポインタ」であるポインタが、から派生しclass Bた のオブジェクトを指している場合、式の動的型は「<コード>D」です。]class DB*p


virtualさて、上部の引用をもう一度見てみると、デストラクタの存在に関係なく、次のコードが正しく行われた場合、未定義の動作が呼び出されることを意味します。

struct B{ virtual ~B(){} };
struct D : B{};

B* p = new D[20];
delete [] p; // undefined behaviour here

どういうわけか、標準の文言を誤解しましたか? 私は何かを見落としましたか?標準がこれを未定義の動作として指定するのはなぜですか?

4

5 に答える 5

32

Base* p = new Base[n]要素のnサイズの配列を作成し、その最初の要素を指します。ただし、要素のサイズの配列を作成します。次に、最初の要素のサブオブジェクトを指します。ただし、配列の最初の要素を参照していませ。これは、有効な式に必要なものです。BasepBase* p = new Derived[n]nDerivedpBase pdelete[] p

もちろんdelete [] p、この場合、Does TheRightThing™を義務付ける(そして実装する)ことは可能です。しかし、それは何が必要でしょうか?dynamic_cast p実装では、配列の要素タイプを何らかの方法で取得し、道徳的にこのタイプを取得するように注意する必要があります。delete[]それから、それは私たちがすでに行っているように平易を行うことの問題です。

それに関する問題は、ポリモーフィズムが使用されているかどうかに関係なく、ポリモーフィック要素タイプの配列のたびにこれが必要になることです。私の意見では、これは、使用しないものにお金を払わないというC++の哲学とは一致しません。しかし、さらに悪いことに、ポリモーフィック対応は、あなたの質問ではほとんど役に立たdelete[] pないので、単に役に立たないのです。要素のサブオブジェクトへのポインタであり、それ以上ではありません。それ以外の点では、アレイとはまったく関係ありません。あなたは確かにそれで(のために)することはできません。したがって、機能しないのは不合理ではありません。ppp[i]i > 0delete[] p

総括する:

  • アレイにはすでに多くの正当な用途があります。配列が多形的に(全体としてまたはのみdelete[])動作することを許可しないことにより、これは、多形要素タイプの配列が、C++の哲学に沿った正当な使用に対してペナルティを科されないことを意味します。

  • 一方、多態的な振る舞いをする配列が必要な場合は、すでに持っているものの観点からそれを実装することが可能です。

于 2011-05-30T03:17:45.303 に答える
12

アイテムを削除するときだけでなく、派生配列をベース配列として扱うのは誤りです。たとえば、要素にアクセスするだけでも、通常は災害が発生します。

B *b = new D[10];
b[5].foo();

b[5]はのサイズを使用して、Bアクセスするメモリの場所を計算します。サイズが異なる場合BDこれは意図した結果につながりません。

std::vector<D>をに変換できないのと同じように、std::vector<B>へのポインタをに変換することはできませD[]B*が、歴史的な理由から、とにかくコンパイルされます。代わりにastd::vectorを使用すると、コンパイル時エラーが発生します。

これは、このトピックに関するC ++FAQLiteの回答でも説明されています。

したがってdelete、この場合、型システムがエラーをキャッチできない場合でも、このように配列を処理することはすでに間違っているため、未定義の動作が発生します。

于 2011-05-30T03:26:04.040 に答える
1

sthの優れた回答に追加するために、この問題をさまざまなオフセットで説明する短い例を書きました。

Derived クラスの m_c メンバーをコメント アウトすると、削除操作がうまく機能することに注意してください。

乾杯、

男。

#include <iostream>
using namespace std;

class Base 
{

    public:
        Base(int a, int b)
        : m_a(a)
        , m_b(b)    
        {
           cout << "Base::Base - setting m_a:" << m_a << " m_b:" << m_b << endl;
        }

        virtual ~Base()
        {
            cout << "Base::~Base" << endl;
        }

        protected:
            int m_a;
            int m_b;
};


class Derived : public Base
{
    public:
    Derived() 
    : Base(1, 2) , m_c(3)   
    {

    }

    virtual ~Derived()
    {
        cout << "Derived::Derived" << endl;
    }

    private:    
    int m_c;
};

int main(int argc, char** argv)
{
    // create an array of Derived object and point them with a Base pointer
    Base* pArr = new Derived [3];

    // now go ahead and delete the array using the "usual" delete notation for an array
    delete [] pArr;

    return 0;
}
于 2017-10-16T07:30:58.230 に答える
0

すべてはゼロ オーバーヘッドの原則に帰着すると思います。つまり、この言語では、配列の要素の動的な型に関する情報を格納できません。

于 2011-05-30T02:57:12.987 に答える
0

IMHOこれは、コンストラクタ/デストラクタを処理するための配列の制限に関係しています。new[]が呼び出されると、コンパイラは強制的にデフォルトのコンストラクタのみをインスタンス化することに注意してください。が呼び出されたときと同様にdelete[]、コンパイラは呼び出しポインタの静的型のデストラクタのみを探す場合があります。

デストラクタの場合、virtual最初に派生クラスのデストラクタを呼び出し、次に Base クラスを呼び出す必要があります。配列の場合、コンパイラーは呼び出しオブジェクト (ここでは Base) 型の静的型を認識する可能性があるため、Base デストラクタのみを呼び出すことになる可能性があります。これはUBです。

そうは言っても、必ずしもすべてのコンパイラで UB であるとは限りません。たとえば、gccが適切な順序でデストラクタを呼び出すとします。

于 2011-05-30T03:02:06.293 に答える