0

次のコードを検討してください。

int three() {
    return 3;
}

template <typename T>
class Foo {
private:
    T* ptr;

public:
    void bar(T& t) { ptr = new T(t); }
    void bar(const T& t) { ptr = new T(t); }
    void bar(T&& t) { (*ptr) = t; } // <--- Unsafe!
};

int main() {
    Foo<int> foo;

    int a = 3;
    const int b = 3;

    foo.bar(a); // <--- Calls Foo::bar(T& t)
    foo.bar(b); // <--- Calls Foo::bar(const T& t)
    foo.bar(three()); // <--- Calls Foo::bar(T&& t); Runs fine, but only if either of the other two are called first!

    return 0;
}

Foo::bar(T&& t)私の質問は、3 番目のオーバーロードがプログラムをクラッシュさせるのはなぜですか? ここで正確に何が起こっているのですか?t関数が戻った後、パラメーターは破棄されますか?

Tさらに、テンプレート パラメーターが、非常にコストのかかるコピー コンストラクターを持つ非常に大きなオブジェクトであると仮定しましょう。Foo::ptrこのポインターに直接アクセスしてコピーを作成せずに、RValue 参照を使用して割り当てる方法はありますか?

4

4 に答える 4

3

この行
void bar(T&& t) { (*ptr) = t; } // <--- Unsafe!
では、初期化されていないポインタを逆参照できます。これは未定義の動作です。オブジェクトのメモリを作成する必要があるため、最初に他の2つのバージョンのbarのいずれかを呼び出す必要があります。
だから私はしますptr = new T(std::move(t));
タイプTが移動をサポートしている場合、moveコンストラクターが呼び出されます。

アップデート

私はそのようなことを提案します。foo内にポインタタイプが必要かどうかわからない:

template <typename T>
class Foo {
private:
    T obj;

public:
    void bar(T& t) { obj = t; } // assignment
    void bar(const T& t) { obj = t; } // assignment
    void bar(T&& t) { obj = std::move(t); } // move assign
};

これにより、メモリリークを回避できます。これは、アプローチでも非常に簡単です。
クラスfooにポインタが本当に必要な場合は、次のようにします。

template <typename T>
class Foo {
private:
    T* ptr;

public:
    Foo():ptr(nullptr){}
    ~Foo(){delete ptr;}
    void bar(T& t) { 
        if(ptr)
            (*ptr) = t;
        else
            ptr = new T(t);
    }
    void bar(const T& t) { 
        if(ptr)
            (*ptr) = t;
        else
            ptr = new T(t);
    }
    void bar(T&& t) { 
        if(ptr)
            (*ptr) = std::move(t);
        else
            ptr = new T(std::move(t));
    } 
};
于 2011-09-23T16:13:53.810 に答える
0

他の 2 つの呼び出しなしで呼び出しただけあると仮定します。foo.bar(three());

なぜそれがうまくいくと思ったのですか?あなたのコードは本質的にこれと同等です:

int * p;
*p = 3;

pは type の有効な変数を指していないため、これは未定義の動作ですint

于 2011-09-23T16:05:23.687 に答える
0

そのコードで失敗する理由はありません。前の呼び出しによって作成されptrた既存のオブジェクトを指し、3 番目のオーバーロードはそのオブジェクトに新しい値を割り当てるだけです。intbar

ただし、代わりにこれを行った場合:

int main() {
    Foo<int> foo;

    int a = 3;
    const int b = 3;
    foo.bar(three()); // <--- UB

    return 0;
}

その行は、オブジェクトへの有効なポインターではないfoo.bar(three());ため、未定義の動作 (例外を意味するものではありません) になります。ptrint

于 2011-09-23T16:11:10.540 に答える
0

ここでの「安全でない」ことは、新しいオブジェクトを ptr に割り当てる前に、 ptr が実際に指しているものの運命について心配する必要があるということです。

foo.bar(three()); 

呼び出す前に、 ptr が実際に何かを指していることを許可する必要があるという意味で安全ではありません。あなたの場合、それはによって作成されたものを指しますfoo.bar(b);

しかしfoobar(b)、によってptr作成されたものを忘れて、新しいオブジェクトを指すようにしますfoobar(a)

より適切なコードは

template<class T>
class Foo
{
    T* p;
public:
    Foo() :p() {}
    ~Foo() { delete p; }

    void bar(T& t) { delete p; ptr = new T(t); }
    void bar(const T& t) { delete p; ptr = new T(t); }
    void bar(T&& t) 
    { 
        if(!ptr) ptr = new T(std::move(t));
        else (*ptr) = std::move(t); 
    } 
}

;

于 2011-09-23T16:31:17.653 に答える