2012-01-17 6 views
2

を呼び出します。C++:STL:ベクトル:削除:デストラクタは、コードは以下の通りです

#include <iostream> 
#include <vector> 
#include <algorithm> 

using namespace std; 

struct A { 
    A(int i = -1): i_(i) { 
    wcout << "Constructor: i = " << i_ << endl; 
    } 
    A(A const &a) { 
    wcout << "Copy constructor: i = " << i_ << " a.i = " << a.i_ << endl; 
    *this = a; 
    } 
    ~A() { wcout << "Destructor: i = " << i_ << endl; } 
    A &operator=(A const& a) { 
    wcout << "Copy assignment operator: i = " << i_ << " a.i = " << a.i_ << endl; 
    i_ = a.i_; 
    return *this; 
    } 
    bool operator==(A const& rhs) { return i_ == rhs.i_; } 
    int get() { return i_; } 
private: 
    int i_; 
}; 

int wmain() { 
    A a[] = {1, 2, 3, 2, 4, 5}; 
    vector<A> v(a, a + sizeof a/sizeof a[0]); 
    wcout << "==== Just before remove ====" << endl; 
remove(v.begin(), v.end(), 2); 
    wcout << "==== Just after remove ====" << endl; 

    return 0; 
} 

出力:

==== Just before remove ==== 
Constructor: i = 2 
Destructor: i = 2 
Constructor: i = 2 
Destructor: i = 2 
Constructor: i = 2 
Destructor: i = 2 
Copy assignment operator: i = 2 a.i = 3 
Constructor: i = 2 
Destructor: i = 2 
Constructor: i = 2 
Destructor: i = 2 
Copy assignment operator: i = 3 a.i = 4 
Constructor: i = 2 
Destructor: i = 2 
Copy assignment operator: i = 2 a.i = 5 
==== Just after remove ==== 

質問は:削除()が実行中にデストラクタが6回と呼ばれる理由?私はこの混乱が明らかにされる必要があります。

注:応答する前に、このコードをシステムで実行してください。 注:MSVCPP 11

+0

あなたの出力は、コードが一致しません。数字はどこから来たのですか? – thiton

+3

デストラクタに 'i_'の値を記録すると、明らかになります。ヒント:あなたの '演算子=='はどのような型ですか? –

答えて

7

質問です:削除()が実行されている あったデストラクタが6回と呼ばれる理由?要約すると

、デストラクタの呼び出しは暗黙的にremove()によってAに変換取得2としなければなりません。このような暗黙の変換の結果が有効範囲外になるたびに、Aのデストラクタが呼び出されます。

これらの暗黙的な変換の理由は、remove()aの各要素と2のすべての要素を比較する必要があるためです。これを行うための唯一の方法は、A::operator==(const A&)を呼び出すことである:タイプconst A&である

bool operator==(A const& rhs) { ... } 

rhsので、コンパイラ:

  1. 呼び出しA(int)A2を変換します。
  2. operator==(const A&)を呼び出します。
  3. A::~A()を呼び出して一時的なものを破棄します。

後者は、見ているデストラクタコールです。

あなたはAに次の比較演算子を追加した場合、あなたがそれらのデストラクタの呼び出しが消え表示されます:あなたはそうのようなremove()をコールした場合

bool operator==(int rhs) { return i_ == rhs; } 

あるいは、あなたはすべてのバー1が表示されますデストラクタの呼び出しが消える:あなたがA::A(int)explicitを作るとしたら

remove(v.begin(), v.end(), A(2)); 

最後に、コンパイラはあなたがでremove()を呼び出すことができませんを最後の引数として使用します(A(2)と呼ぶ必要があります)。

+0

実際、さらに明確にするために、OPはコンストラクタ、コピーコンストラクタ、および割り当てを記述し、それらのログも作成して、作成されている一時オブジェクトの数を確認できます。 –

+1

@DaddyM代わりに 'remove(v.begin()、v.end()、A(2))'を書き、何が起こるかを見てください。 'remove'は' A 'を取るのではなく、あなたが与えるもの(この場合は 'int')のテンプレートであることを覚えておいてください。 –

+2

説明が一部間違っています。 'remove'は2を' int'とみなし、それぞれの等価チェックのために一時的なものを構築します。 'std :: remove'は値を比較するオブジェクトを渡すべきではありません。 – visitor

3

問題は次のとおりです。remove()が実行中にが実行されたのに対し、デストラクターが6回呼び出されたのはなぜですか?

std::removeは要素を並べ替えるだけですが、ベクトルから何も削除しません。並べ替えの過程で、いくつかの要素がコピー構築されます。

#include <iostream> 
#include <vector> 
#include <algorithm> 

using namespace std; 

struct A { 
    A(int i = -1): i_(i) {cout << "I was created" << i_ << endl;} 
    ~A() { 
    cout << "I was destroyed" << i_ << endl; 
    } 
    A(const A& o):i_(o.i_) 
    { 
    cout << "I was copied" << i_ << endl; 
    } 
    A& operator=(const A& o) 
    { 
    i_=o.i_; 
    cout << "I was assigned" << i_ << endl; 
    return *this; 
    } 
    int get() { return i_; } 
    bool operator==(A const& rhs) { return i_ == rhs.i_; } 
private: 
    int i_; 
}; 

int main() { 
std::cout<<"start"<<std::endl; 
    A a[] = {1, 2, 3, 2, 4, 5}; 

    std::cout<<"creating"<<std::endl; 
    vector<A> v(a, a + sizeof a/sizeof a[0]); 
    std::cout<<"removing"<<std::endl; 
    remove(v.begin(), v.end(), 2); 
    std::cout<<"end"<<std::endl; 
} 

removeがベクトルの終わりに「除去」要素を配置されている:


次にコードは何が起こるかを詳細に示しています。

+1

6回破棄された要素はすべて "2"です – DaddyM

1

これはかなり単純です。 std::removeの機能とその動作を理解する必要があります。ヒント:ではない要素をベクターから削除します。代わりにあなたのコレクションの背面に移動します。ベクトル内の要素を移動すると元の要素が破壊されます。だから、これはあなたのデストラクタコールの一部です。

他の部分は、一時的なオブジェクトから来ている - あなたはstd::removeに最後のパラメータとしてintstruct Aのではなく、インスタンス)を通過したことから、Aのインスタンスは、比較のために構築する必要があります。あなたのコードをもっと徹底的に練習したい場合は、1パラメータのコンストラクタの前にキーワードexplicitを付けることを習慣にしてみてください。このような一時的なオブジェクトを排除することは非常に効率的です。その後、明示的に比較対象を作成する必要があります:

remove(v.begin(), v.end(), A(2)); 
+0

実際には、 'std :: remove'の動作は、項目が新しい端を越えたままである状態に関しては指定されていません。彼らは破壊可能な状態にありますが、それ以上のことは保証されません。実際には、インプリメンテーションはそれらを「移動」または「スワップ」することなく、またはそれらからコピーしてそのまま残します。 –

+0

私はこの点をISO 14882-2011で指摘しておかなければなりません "注:retが返される値である範囲[ret、last]の各要素は、アルゴリズムが要素を排除することができるので、有効だが不特定の状態です。 .. "これらのオブジェクトが*特定の*前提条件を満たしていないことを意味します。これは、破壊だけでなく、*なしの*前提条件を持つ操作に対して、それらを完全に有効にします。また、それは無関係です。もともとretの前にあったオブジェクトと "削除された"オブジェクトは破壊されているに違いありません。それが私の指摘でした。 – bronekk

+0

*部分的には、引用の終わりも重要です:*アルゴリズムは、**と置き換えたり、元々その範囲にあった要素から移動したりすることで要素を排除することができるためです。 「削除された」要素が破壊されたのか、「過去の最後の要素」の要素と交換されたのかは不明です。スワッピング戦略では、デストラクタはまったく呼び出されません。 –

2

std::removeは、使用状況、(2)はintと推定される第三引数で

template<typename ForwardIterator, typename Tp> 
ForwardIterator remove(ForwardIterator first, ForwardIterator last, 
    const Tp& value); 

として宣言されています。 int変数がAオブジェクトに対して直接的に比較されないので、第1の一時的、一時的に破壊され、比較の終わりにすべての

if (*it == value) ... 

のために構築されなければなりません。

(すべてのすべてで、あなたは非明示的な単一引数、別称、変換コンストラクタを持つから生じた、隠されたパフォーマンスの問題を抱えている。)