2

私はコンストラクタとデストラクタの技術的な理由に少し慣れていません。オブジェクトの開始状態、終了状態、メモリ位置、値、およびコンストラクタとデストラクタが呼び出されたときに表示する関数を使用してプログラムを作成しました。

彼らがなぜ呼ばれるのか、そして彼らが実際に何をしているのか理解するのに苦労しています。テスト実行の結果と使用しているコード、そして私が知っていることを投稿します。


結果:

Constructor called on 0x7fffc053f070

Initial state: 
Location: 0x7fffc053f070
  Value: 0

--- FOO ----
Location: 0x7fffc053f070
  Value: 1

--- BAR ----
Location: 0x7fffc053f080
  Value: 2

Destructor called on 0x7fffc053f080

--- BAZ ----
Location: 0x7fffc053f070
  Value: 2


Final state: 
Location: 0x7fffc053f070
  Value: 2

Destructor called on 0x7fffc053f070


コード:

#include <iostream>
#include <vector>

using namespace std;

//Short memory addresses are on the heap
//Long memory addresses are on the stack

class A {

public:

A(){
    m_iValue = 0;
    cout << "Constructor called on " << this << endl;
}

/*      A(const A & a){
            m_iValue = a.m_iValue;
            cout << "Copy constructor called on " << this << endl;
    }
*/
void increment(){
    m_iValue++;
}

void display(){
    cout << "Location: " << this << endl;
    cout << "  Value: " <<m_iValue << endl;
    cout << endl;
}

virtual ~A(){
    cout << "Destructor called on " << this << endl;
}

private:
    int m_iValue;
};

void foo(A & a){
    a.increment();
    a.display();
}

void bar(A a){
    a.increment();
    a.display();
}

void baz(A * a){
    a->increment();
    a->display();
}

void blah(vector<A*> vA){
    vA.back()->display();
    delete vA.back();
    vA.pop_back();
}

int main(int argc, char * argv[]){

    A a;

    cout << "Initial state: " << endl;
    a.display();

    cout << endl;

    foo(a);
    bar(a);
    baz(&a);
    cout << endl;
    cout << "Final state: " << endl;
    a.display();

    return 0;
}

私が信じていることは起こっている:

したがって、コンストラクタは1回呼び出され、デストラクタは2回呼び出されます。コンストラクターは、オブジェクトがmainで作成されるときに呼び出されます。foo()では、m_iVariableが参照によって渡され、関数はメモリ内のその場所にあるオブジェクトのm_iValueをインクリメントします。したがって、プログラムは値を1(0からインクリメント)として表示します。

これは私が混乱するところです。3番目の場所は最初の2つの場所とは異なります。オブジェクトはbar()に直接渡されます。コンストラクターを呼び出さずに場所がどのように異なるのか、またはインクリメント後にデストラクタが呼び出されて値が2になる理由がわかりません。

ただし、bazも値をインクリメントします。つまり、バーは実際には何もしなかったということですか?バーが新しいメモリ位置を表示して破棄する方法がまだわかりませんが、構築することはありません。


すべてのテキストで申し訳ありませんが、何でも役に立ちます。ありがとう!

ああ、コメントアウトされたコード、そして関数blahは他のことに使用されており、この質問には相対的ではありません。

4

5 に答える 5

3

に値を渡すとbar()、その関数に対してローカルな新しいオブジェクトが作成されます。そのオブジェクトは、戻るときに破棄されbar()ます。これは、コピーコンストラクターによって初期化されますA(A const &)。これは、自分で宣言しないため、暗黙的に生成されます。コピーコンストラクターのコメントを外すと、それが発生していることがわかります。

一般に、重要なデストラクタを持つオブジェクトをコピーできるようにする場合は注意が必要です。このようなクラスは通常、デストラクタで解放されるリソースを管理します。コピーが同じリソースを管理しようとしないように注意する必要があります。そのようなクラスを作るときは、常に三つのルールを覚えておいてください。

于 2013-02-28T17:19:34.673 に答える
2

main非常に一般的な用語では、コンストラクターは、ルーチンで行うようにオブジェクトをスタック上に作成することによって暗黙的にオブジェクトを初期化するとき、またはを使用してオブジェクトを明示的に割り当てて初期化するときに呼び出されますnew引数を値でメソッドに渡すと、初期化の一種であるコピーコンストラクターを使用してオブジェクトのコピーを作成する効果があることに注意することが重要です。

デストラクタは、スタックに割り当てられたオブジェクトがスコープから外れたとき、または動的に割り当てられたオブジェクトが。で解放されたときに呼び出されますdelete

メソッドの1つが値によって渡され、そのコピーが作成され、後でメソッドが完了すると破棄されるため、ここでは2つのデストラクタ呼び出しが表示されています。これら2つのオブジェクトのメモリアドレスは異なります。

値渡しの場合、コピーに加えられた変更はオリジナルに反映されません。これが、多くのC ++アプリケーションで、メソッドが変更を許可するように参照foo(A& a)によって、またはのように変更が許可されないことを明確にするためにconstfoo(const A& a)参照によって物事を渡す理由です。ポインタは同じ目的で使用されることもあります。

ここで問題が発生する理由は、デストラクタ、コピーコンストラクタ、およびコピー代入演算子に関する3つのルールを認識していないためです。

于 2013-02-28T17:16:01.353 に答える
0

コンストラクタとデストラクタにprintステートメントを配置するだけで、実行時に動作することを確認するのは常に楽しいことです。上記の良い答えがたくさんあるので、私は何も言いませんが、これを行うことは私のプログラミングのキャリアの早い段階で私を助けました。

于 2013-02-28T17:56:27.960 に答える
0

まず、出力にコンストラクタとデストラクタの呼び出しの数が異なる理由は、そのような呼び出しがすべて表示されるわけではないためです。あなたの場合、すべてのコンストラクターを考慮せずに、コードをインストルメント化する試みの失敗です。一般に、コンストラクターが例外をスローすることによっても発生する可能性があります。これは、コンストラクター呼び出しの数と一致するのは成功したコンストラクターcakkの数だけであり、オブジェクトが破棄されない(動的に割り当てられ、削除されないなど)ことが原因である可能性があるためです。


言語の非常に低レベルの機能の(乱用)使用を無視して、C++ルールは次のことを保証するように設計されています

  • タイプTのオブジェクトごとに、他の呼び出しの前に発生するTコンストラクター呼び出しが1つだけあります。

  • 成功するコンストラクタ呼び出しごとに、対応するデストラクタ呼び出しがあります(オブジェクトが破棄された場合)。

これらのルールは、オブジェクト階層で再帰的に保持されます。サブオブジェクト(データメンバー)を持つオブジェクトは、そのような階層の例です。サブオブジェクトは、データメンバーになる代わりに、基本クラスのサブオブジェクトにすることができます。

コンストラクターの責任は、技術的には、生のメモリの一部を型指定された意味のあるオブジェクトに変換することです(これを、既存の値を新しい値に置き換え、場合によっては以前に割り当てられたメモリの割り当てを解除するコピー代入演算子と比較してください)。設計レベルでは、コンストラクターの主な責任は、パブリックメソッドの呼び出し間のオブジェクトの状態について想定できるものは何でも、クラス不変条件を確立することです。デストラクタの仕事は、たとえばリソースを解放するためにクリーンアップすることです。

構築の失敗は、言語の観点から、コンストラクターが例外をスローした場合です。

new式は、メモリ割り当てと構築の間にトランザクションのような非常に強力な結合を提供します。これにより、2つの引数のセットを提供できます。割り当て関数の最初の引数のセットと、コンストラクターの2番目の引数のセットです。以下に示す場合を除いて、コンストラクタが失敗すると、メモリの割り当てが自動的に解除され、例外が伝播されます。つまり、一般に、両方が成功するか、副作用が取り消され、呼び出し元のコードに失敗が通知されます。

クリーンアップが行われない唯一の例外は、カスタム割り当て関数、いわゆる「配置新規」演算子を定義し、対応する割り当て解除関数を提供できなかった場合です。カスタム割り当て解除関数が必要な理由はわかりません。実際、これが暗黙的に呼び出される唯一の状況です。

于 2013-02-28T17:15:49.173 に答える
0

メソッドbar(A a)では、呼び出されるのはコピーコンストラクターA(const A& a) です。宣言していない場合は、コンパイラーによって暗黙的に作成され、各メンバーのコピーコンストラクターを呼び出します。

このメソッドを追加するだけです。

A(const A& a){
  m_iValue = a.value;
  cout << "Copy constructor called on " << this << endl;
}
于 2013-02-28T17:16:18.217 に答える