9

C++ では、クラスに動的に割り当てられたデータが含まれる場合、通常、コピー コンストラクター、operator=、およびデストラクターを明示的に定義するのが合理的です。しかし、これらの特別なメソッドの活動は重複しています。より具体的には、 operator= は通常、最初に何らかの破壊を行い、次にコピーコンストラクターと同様の対処を行います。

私の質問は、同じコード行を繰り返さずに、プロセッサが不要な作業 (不要なコピーなど) を実行する必要なく、これを最善の方法で記述する方法です。

私は通常、2つの支援方法に行き着きます。1 つは構築用、もう 1 つは破壊用です。1 つ目は、コピー コンストラクターと operator= の両方から呼び出されます。2 つ目は、デストラクタと operator= によって使用されます。

コード例は次のとおりです。

    template <class T>
    class MyClass
    {
        private:
        // Data members
        int count;
        T* data; // Some of them are dynamicly allocated
        void construct(const MyClass& myClass)
        {
            // Code which does deep copy
            this->count = myClass.count;
            data = new T[count];
            try
            {
                for (int i = 0; i < count; i++)
                    data[i] = myClass.data[i];
            }
            catch (...)
            {
                delete[] data;
                throw;
            }
        }
        void destruct()
        {
            // Dealocate all dynamicly allocated data members
            delete[] data;
        }
        public: MyClass(int count) : count(count)
        {
            data = new T[count];
        }
        MyClass(const MyClass& myClass)
        {
            construct(myClass);
        }
        MyClass& operator = (const MyClass& myClass)
        {
            if (this != &myClass)
            {
                destruct();
                construct(myClass);
            }
            return *this;
        }
        ~MyClass()
        {
            destruct();
        }
    };

これも正しいですか?そして、このようにコードを分割するのは良い習慣ですか?

4

3 に答える 3

8

1 つの最初のコメント: は、破壊ではなく、構築によって開始されoperator=ますそうしないと、構築が例外によって終了した場合に、オブジェクトが無効な状態のままになります。このため、コードは正しくありません。(自己代入をテストする必要性は、通常、代入演算子が正しくないことを示していることに注意してください。)

これを処理するための古典的な解決策は swap イディオムです: メンバー関数 swap を追加します:

void MyClass:swap( MyClass& other )
{
    std::swap( count, other.count );
    std::swap( data, other.data );
}

スローしないことが保証されています。(ここでは、どちらもスローできない int とポインターを交換するだけです。) 次に、代入演算子を次のように実装します。

MyClass& MyClass<T>::operator=( MyClass const& other )
{
    MyClass tmp( other );
    swap( tmp );
    return *this;
}

これは単純で簡単ですが、データの変更を開始する前に、失敗する可能性のあるすべての操作が終了しているソリューションは受け入れられます。たとえば、コードのような単純なケースの場合:

MyClass& MyClass<T>::operator=( MyClass const& other )
{
    T* newData = cloneData( other.data, other.count );
    delete data;
    count = other.count;
    data = newData;
    return *this;
}

( wherecloneDataは、あなたが行うことのほとんどを行うメンバー関数ですがconstruct、ポインターを返し、 で何も変更しませんthis)。

編集:

最初の質問とは直接関係ありませんが、一般的に、そのような場合、in (または、または何でも)を実行したくありません。これにより、デフォルトのコンストラクターを使用してすべてが構築され、それらが割り当てられます。これを行う慣用的な方法は、次のようなものです。new T[count]cloneDataconstructT

T*
MyClass<T>::cloneData( T const* other, int count )
{
    //  ATTENTION! the type is a lie, at least for the moment!
    T* results = static_cast<T*>( operator new( count * sizeof(T) ) );
    int i = 0;
    try {
        while ( i != count ) {
            new (results + i) T( other[i] );
            ++ i;
        }
    } catch (...) {
        while ( i != 0 ) {
            -- i;
            results[i].~T();
        }
        throw;
    }
    return results;
}

ほとんどの場合、これは別の (プライベート) マネージャー クラスを使用して行われます。

//  Inside MyClass, private:
struct Data
{
    T* data;
    int count;
    Data( int count )
        : data( static_cast<T*>( operator new( count * sizeof(T) ) )
        , count( 0 )
    {
    }
    ~Data()
    {
        while ( count != 0 ) {
            -- count;
            (data + count)->~T();
        }
    }
    void swap( Data& other )
    {
        std::swap( data, other.data );
        std::swap( count, other.count );
    }
};
Data data;

//  Copy constructor
MyClass( MyClass const& other )
    : data( other.data.count )
{
    while ( data.count != other.data.count ) {
        new (data.data + data.count) T( other.date[data.count] );
        ++ data.count;
    }
}

(そしてもちろん、代入のための swap イディオム)。これにより、例外の安全性を失うリスクなしに、複数のカウント/データのペアが可能になります。

于 2013-07-10T10:09:25.130 に答える
0

コンストラクトを宣言したり仮想を破棄したりしない限り、固有の問題はありません。

コンストラクタ、コピー演算子、およびデストラクタに完全に専念している、Effective C++ (Scott Meyers) の第 2 章に興味があるかもしれません。

コードが適切に処理していない例外については、「より効果的な C++ (Scott Meyers)」の項目 10 と 11 を検討してください。

于 2013-07-10T10:07:21.683 に答える
0

最初に右辺をコピーしてから、それと交換することにより、割り当てを実装します。このようにして、上記のコードでは提供されない例外の安全性も得られます。メンバーポインターが割り当て解除されたデータを参照し、破棄時に再度割り当て解除され、未定義の動作を引き起こすため、destruct() が成功した後に construct() が失敗すると、コンテナーが壊れてしまう可能性があります。

foo&
foo::operator=(foo const& rhs)
{
   using std::swap;
   foo tmp(rhs);
   swap(*this, tmp);
   return *this;
}
于 2013-07-10T10:03:49.260 に答える