2

次のコードを検討してください。このコードは、本 Object Oriented Programming With C++ !-Chapter 12.Templates からの抜粋です。

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
using namespace std;
class vc
{
    int size2;
    int *v;
public :

    vc(int size1);
    vc(int *a);
    ~vc()
    {
        printf("\n calling destructor");
    }
    int operator *(vc );
};
vc::vc(int size1)
{
    v = new int[size2=size1];
    for(int i=0;i<this->size2;i++)
        v[i]=0;
}
vc::vc(int a[])
{

    for(int i=0;i<this->size2;i++)
    {
        v[i]=a[i];

    }
}
int vc::operator *(vc v1)
{
    int total=0;
    for(int i=0;i<size2;i++)
        total+=(v[i]*v1.v[i]);
    return total;
}

int main()
{
    int a[3]={1,2,3};
    int b[3]= {5,6,7};
    vc v1(3),v2(3);
     v1=a;
     v2=b;
    int total = v1*v2;
    cout << total;
    return 0;
}

まず、このコードは正常に機能していません。出力として38を表示する必要があります。このコードのデバッグを開始したとき、このラインの後に3割り当てられていることがわかりました。ただし、次の行を実行している間、コントロールは2番目のコンストラクターに渡され、ごみの値が表示されます。さらに、デストラクタはライン後に呼び出され、次のラインの後にも同じことが起こります。size2vc v1(3),v2(3);size2v1=a

最終出力:

calling destructor
calling destructor
calling destructor0

Destructorが3回呼び出されるのはなぜですか?このコードは間違っていますか?

4

5 に答える 5

8

を呼び出すときは、メソッドv1*v2に渡しますv2

int vc::operator *(vc v1)

のローカル コピーを作成しますv2。したがって、追加のvcインスタンスがあります。

あなたの最初の疑問に対する答えは、

しかし、次の行の実行中に、制御が 2 番目のコンストラクターに渡され、size2 がガベージ値を示し、3 回実行されません。

なぜなら

v1 = a;

vcを呼び出して一時的な を作成し、vc::vc(int a[])それを に割り当てv1ます。ただし、このコンストラクターは を初期化しませんでしsize2た。したがって、ガベージ値を取得します。

よりクリーンなアプローチは、配列とそのサイズを渡すことです。

vc::vc(int a[], int size1) {                                                                                       
    v = new int[size2=size1];                                                                                      
    for(int i=0; i<size2; i++)                                                                                     
        v[i] = a[i];                                                                                                 
}

int main()
{
    int a[3]={1,2,3};
    int b[3]= {5,6,7};
    vc v1(a, 3), v2(b, 3); 
    int total = v1*v2;
    cout << total;
    return 0;
}

それからtotal38になります。

于 2013-08-09T15:17:14.233 に答える
5
v1=a;

その行は、実際には割り当てだけではありません。これは、一時オブジェクトの構築と、それを既に作成されたオブジェクトに割り当てることです。int[]コンパイラが認識できるクラスに割り当てる唯一の方法は、vc(int a[])コンストラクターを使用して一時オブジェクトを作成することですvcが、そのコンストラクターは size2 を初期化しません。これが問題です (「しかし、次の行を実行している間、 、制御は 2 番目のコンストラクターに渡され、size2 はガベージ値を示します。")

このテンポラリが作成された後、暗黙の代入演算子 (指定しなかったため、コンパイラによって自動的に作成されます) は、このオブジェクトのメンバーを作成済みのオブジェクトにコピーします。

これは暗黙の変換と呼ばれます (たとえば、https ://publib.boulder.ibm.com/infocenter/lnxpcomp/v8v101/index.jsp?topic=%2Fcom.ibm.xlcpp8l.doc%2Flanguage%2Fref%2Fcplr384.htm を参照してください)。 )。それを防ぎたい場合は、コンストラクターでexplicitキーワードを使用します。vc(int a[])

後付け: 私の最初の答えは、最後に述べた主な質問に実際には答えませんでした (デストラクタが 3 回呼び出される理由)。ここで他の人がすでに非常にうまく答えていると思います.1つのオブジェクトは、そこで行う値によるパラメーターの受け渡しのために、vc内部でローカルに作成されます。operator*

ここで、デストラクタが実際に 5 回呼び出されない理由を尋ねることができます (暗黙的に作成されたオブジェクトの場合も 2 回)。コンパイラが新しいオブジェクトの実際の作成を何らかの形で最適化するためだと思います(戻り値の最適化である可能性があります。間違っている場合は誰かが私を修正してください!)。

于 2013-08-09T15:24:06.860 に答える
3

v1*v2は 3 番目の一時オブジェクトを作成するため、作成した 2 つのオブジェクトだけでなく、そのデストラクタも呼び出されます。

于 2013-08-09T15:16:31.050 に答える
2

余分なデストラクタの理由である値ではなく、参照によって渡す必要があります。

   int vc::operator *(vc &v1)

そして、ニャルラトテプの即答。バグを防ぐ明示的なキーワードを追加することは別として。

あなたはこれを見逃しているだけです

void vc::operator=(int* a)
{
    for(int i=0;i<this->size2;i++)
{
    v[i]=a[i];

}
}
于 2013-08-09T15:23:55.983 に答える
1

他の人が言ったように、デストラクタは、呼び出さoperator*(vc v1)れると一時的なローカル コピーが構築されるため、 3 回呼び出さv1ます

nyarlathotepの答えは正しいです。innosamによる提案はほぼ理想的です。

他にも具体的なポイントをいくつか追加したいと思いました。

2 つの注意事項:

  • このoperator*メソッドの場合のように、(コピーや変更ではなく) 呼び出された関数内で参照するだけのオブジェクトのインスタンスを渡す場合は、常にconst への参照で渡します。operator*(const vc& v1)
  • v1 = a nyarlathotepが述べたように、整数配列のインスタンス化vc(int a[])への暗黙的な変換のためにのみ、コンストラクターを呼び出します。これは、このコンストラクターにキーワードを追加することで回避できます。これにより、コンパイラーはそのような変換を許可しないように指示されます。avcexplicit

例をmain同じにして、探している答えを生成するためのコード変更の最小数 (38) は次のとおりです。

  • operator*type の引数を取るように変更しますconst vc&
  • カスタム代入演算子を定義します: vc& operator=(int src[]). その実装は、コンストラクターの実装とまったく同じにすることができますvc(int a[])

vc(int a[])これら 2 つの変更により、コンストラクターが呼び出されなくなったことに気付くでしょう。

さらに、これは本の例にすぎないことはわかっていますが (nyarlathotep と同様に、これがすべきでないことの例であることを願っています)、このクラスには追加の問題があり、いくつか指摘したいと思います。彼ら:

  • デストラクタにメモリ クリーンアップ (つまりdelete[]) を追加する必要があります。
  • このクラスはメンバー変数にメモリを動的に割り当てるため、既定のコピー コンストラクターと既定の代入演算子は、ディープ コピーではなくメンバー変数の浅いコピーのみを実行するため、十分ではありません。この動作は、提供されたコード (上記の 2 つの変更を加えたもの) では呼び出されませんが、将来のためにこれを覚えておいてください。
于 2013-08-09T16:50:36.517 に答える