4

I have trouble understanding why the following code doesn't construct and destruct the two objects I create the way I'd expect:

#include <iostream>

class MyClass {

    int myVar;

public:
    MyClass(int x) {
        myVar = x;
        std::cout << "constructing " << myVar << ", " << (long)this << std::endl;
    }

    ~MyClass() {
        std::cout << "destructing " << myVar << ", " << (long)this << std::endl;
    }
};

int main(int argc, const char * argv[])
{
    MyClass a = MyClass(1);
    a = MyClass(2);
    return 0;
}

I'd think that inside main I first create an object with the value 1, then create a new one with value 2. And each of the objects gets constructed and destructed, so that I'd expect to see the following output:

constructing 1, 3456
constructing 2, 6789
destructing 1, 3456
destructing 2, 6789

However, I get this:

constructing 1, 3456
constructing 2, 6789
destructing 2, 6789   <- note the "2"
destructing 2, 3456

Update: I've added output of the object's address (this) so that one can better see which object does what.

When I use "new MyClass" instead, I do not run into this weird effect.

What is causing this, and, understanding my goal, what is the right way to avoid similar mistakes in the future?

While this example looks harmless, I ran into crashes of my code because I had allocated other objects in the constructor and freed them in the destructor. This did lead to freeing the objects when the object was still in use.

Conclusion

Now that all my questions are answered, let me summarize:

  1. In the above example I'm using "myVar", which doesn't show even the issue that caused me to bring up this question. My apologies for that.
  2. The actual issue I had with the code is that I was not using a simple int var but an array that I created with "new" in the destructor, and freed with delete in the destructor. And with that, the array would get deleted twice, leading to incorrect data in my program.
  3. The fix is to not use a simple pointer to the array but a reference counting pointer, so that, when it gets copied by the assignment operator, it increases the refcount, thereby preventing premature release of it.
  4. In general, the effect I've shown here is nothing dangerous - it doesn't damage anything as I had gotten the impression. The dangerous part was that I didn't use ref counting ptrs.
4

8 に答える 8

11

この行a = MyClass(2);はデストラクタを呼び出さMyClass::operator=ず、実装していない代入演算子 ( ) を呼び出すため、コンパイラが提供します。何も「出力」しないため、表示されません。

2 回取得する理由destrucing 2は、行の直後にa = MyClass(2);一時MyClass(2)オブジェクトが破棄されるためです。次にmain、変数の最後でa破棄され、myVar現在は 2 であるため、再び 2 が出力されます。

于 2013-08-14T14:03:15.387 に答える
3
a = MyClass(2);

operator=コンパイラが提供するコピー代入演算子を使用します。これが、 が表示される理由ですdestructing 2

したがって、コピー中に a.myVar は の2代わりに値を取得します1

一時オブジェクトは、次の行のセミコロンの後に破棄されます。

a = MyClass(2);
//             ^- Here

そしてブロックの最後に、aまた破壊されます。


ここのすべてのプロセス:

int main(int argc, const char * argv[])
{
    MyClass a = MyClass(1);    // Create an object
    a = MyClass(2); // Create a temporary object and use the operator= to proceed to the copy, now a.intVar = 2
                // ^- Here the temporary object is destructed 
    return 0;
}               // a is now destructed
于 2013-08-14T14:05:52.397 に答える
3

作成した cout ステートメントは、C++ プログラムの内部で何が起こっているかを理解するのに役立つ中間レベルのデバッグ ツールと見なす必要があります (実際に低レベルのアセンブリ コードを掘り下げる必要はありません)。投稿されたコードを次のように少し変更し、コンパイラで生成されたデフォルトのコンストラクタと代入演算子を、コンパイラで生成されたものと同じように効果的に動作するものに置き換えました (追加していない場合は、それ自体で十分です)。内部で何が起こっているかを確認するための cout ステートメント)...

#include <iostream>

class MyClass {

    int myVar;

public:
    MyClass(int x) {
        myVar = x;
        std::cout << "                            constructing " << myVar << ", " << this << std::endl;
    }

    ~MyClass() {
        std::cout << "                            destructing  " << myVar << ", at " << this << std::endl;
    }

    MyClass() {
        myVar = 999;
        std::cout << "                            constructing " << myVar << ", at " << this << std::endl;
    }

    MyClass& operator=(const MyClass& rhs) {
        std::cout << "                            object " << myVar << " (at " << this <<
                ") = object " << rhs.myVar << " (at " << &rhs << ")\n";
        myVar = rhs.myVar;
        return *this;
    }

    friend std::ostream& operator<<(std::ostream& s, const MyClass& m);
};

std::ostream& operator<<(std::ostream& s, const MyClass& m) {
    s << m.myVar;
}

int main(int argc, const char * argv[])
{
    MyClass a = MyClass(1);   // <---- the way you initialize 'a'
//    MyClass a(1);   //   // <---- another way to initialize 'a'
    std::cout << "Variable 'a' is now: " << a << "\n";
    std::cout << "Now setting 'a' to 2...\n";
    a = MyClass(2);
    std::cout << "Variable 'a' is now: " << a << "\n";
    return 0;
}

このようにコーディングして、中間レベルのデバッグ用の cout ステートメントを右側にインデントし、(インデントされていない) cout ステートメントを追加して、プログラマーが中間レベルのデバッグを行っていない場合に通常気にかけていることを示しました。これを実行すると、次のようになります。

                            constructing 1, 0xbfcbfb48
Variable 'a' is now: 1
Now setting 'a' to 2...
                            constructing 2, 0xbfcbfb4c
                            object 1 (at 0xbfcbfb48) = object 2 (at 0xbfcbfb4c)
                            destructing  2, at 0xbfcbfb4c
Variable 'a' is now: 2
                            destructing  2, at 0xbfcbfb48

プログラマーが通常気にするのは左側のものです。これは、最初に投稿した C++ プログラムが提供するものとまったく同じです。MyClass はポインターではなく値を格納していることに注意してください。クラスのデータが単純な値の場合、例は問題なくコーディングされており、バグはありません。クラスに代わりにポインターが含まれている場合、実際には、デフォルトのコンストラクターと代入演算子 (または、上記で示したようなデフォルトのもののように動作するユーザー定義のもの) は、ポイントされたオブジェクトの浅いコピーを提供するため、もはや十分ではありません。 -データへ。あなたのクラスは何らかの形式のスマート ポインターを組み込むか、ポイント先のリソースのコピーを手動で処理する必要があります。これには、おそらく効率のために参照カウントが含まれます。ある種のスマート ポインターは、おそらくより安全な方法です。

于 2013-08-14T15:19:31.877 に答える
2

コンパイラは最初の呼び出しを最適化します。

MyClass a = MyClass(1);

構築の代わりに 1 つのコンストラクターを呼び出してから、コピー コンストラクターを呼び出します。ただし、2行目では:

a = MyClass(2);

最初に一時オブジェクトが作成され、次にそれがに割り当てられます。次に発生するのは、一時オブジェクトが破棄され (したがって最初のdestructing 2)、次にa破棄されることです (したがって 2 番目のdestructing 2)。

a が破棄されたときに が出力される理由destructing 2は、定義していないクラスのデフォルトの代入演算子が作成され、この代入演算子が の値をコピーするためですmyVar

于 2013-08-14T14:03:11.807 に答える
1

プログラムが main の最後に到達すると、その時点でのa変数myVarの値が 2 である を破棄します。代わりに次のように記述した場合:

 MyClass a = MyClass(1);
 MyClass b = MyClass(2);

期待される出力が表示されます。

于 2013-08-14T14:02:58.527 に答える
1

あなたが言うとき a = MyClass( 2 ); デフォルトの代入演算子をオブジェクト a に適用します。この場合、a.myVar の値は 2 に変更されます。

代わりに試してください:

int main(int argc, const char * argv[])
{
    MyClass a( 1 );
    MyClass b( 2 );
    return 0;
}
于 2013-08-14T14:05:44.417 に答える
1

質問には答えましたが、著者の実際のプロジェクトがクラッシュする理由を説明する必要があると思います。

いくつかのオブジェクトを含むいくつかのクラスがあります。このオブジェクトはコンストラクターで作成され、デストラクターで削除されます。

class SomeClass
{
    public:
        SomeClass(int param) { mObject = new SomeObj(param); }
        ~SomeClass() { delete mObject; }
    private:
        SomeObj * mObject;
}

次のようなことをしているとき

int main(int argc, const char * argv[])
{
    SomeClass a = SomeClass(1);//1
    a = SomeClass(2);//2
    return 0;//3
}

1 行目で SomeObj コンストラクターを呼び出し、次に 2 行目で呼び出しています。その後、 私たちのために自動生成された を呼び出し
SomeClass::operator=(SomeClass& rhs)てい ます。

{ mObject = rhs.mObject; }

object1.mObject = object2.mObject;
//old object1.mObject is leaked now, we have no pointer to it.
delete object2; // it was temporary, its lifetime is just one line of code
//it calls
delete object2.mObject; // it equals to delete object1.mObject, because both pointers point to same object
delete object1;//after end of main()
//it calls
delete object1.mObject; // ERROR! object was deleted

したがって、C++ には何の問題もありません ;)

于 2013-08-14T15:34:03.900 に答える