避免在复制构造函数和operator =中重复相同的代码

Joh*_*per 5 c++ destructor copy-constructor code-sharing assignment-operator

在c ++中,当类包含动态分配的数据时,显式定义复制构造函数operator =和destructor通常是合理的.但这些特殊方法的活动重叠.更具体地说,operator =通常首先进行一些破坏,然后它会复制类似于复制构造函数中的那个.

我的问题是如何在不重复相同代码行的情况下以最佳方式编写此代码,而无需处理器执行不必要的工作(如不必要的复制).

我通常最终得到两种帮助方法.一个用于施工,一个用于破坏.第一个是从复制构造函数和operator =调用的.第二个是析构函数和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();
        }
    };
Run Code Online (Sandbox Code Playgroud)

这甚至是正确的吗?以这种方式拆分代码是一个好习惯吗?

Jam*_*nze 5

一个初始点评:operator=启动自毁通过,而是通过构建.否则,如果构造通过异常终止,它将使对象处于无效状态.因此,您的代码不正确.(注意,测试自我赋值的必要性通常表明赋值运算符正确.)

处理这个的经典解决方案是交换习惯用法:你添加一个成员函数交换:

void MyClass:swap( MyClass& other )
{
    std::swap( count, other.count );
    std::swap( data, other.data );
}
Run Code Online (Sandbox Code Playgroud)

保证不扔.(这里,它只是交换一个int和一个指针,它们都不能抛出.)然后你将赋值运算符实现为:

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

这很简单直接,但是在开始更改数据之前完成所有可能失败的操作的任何解决方案都是可以接受的.对于像您的代码这样的简单情况,例如:

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

(在哪里cloneData是一个成员函数,它完成你所做的大部分工作construct,但返回指针,并且不会修改任何内容this).

编辑:

不直接关系到你最初的问题,但总体上,在这种情况下,你希望做一个new T[count]cloneData(或construct,或其他).这构造了所有T的默认构造函数,然后分配它们.这样做的惯用方法是这样的:

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;
}
Run Code Online (Sandbox Code Playgroud)

大多数情况下,这将使用单独的(私有)经理类完成:

//  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;
    }
}
Run Code Online (Sandbox Code Playgroud)

(当然,还有用于转让的交换习语).这允许多个计数/数据对,而没有任何失去异常安全的风险.


小智 0

通过首先复制右侧然后与之交换来实现分配。通过这种方式,您还可以获得异常安全性,这是上面的代码所没有提供的。否则,当 destruct() 成功后,construct() 失败时,您可能会得到一个损坏的容器,因为成员指针引用了一些已释放的数据,并且在销毁时将再次释放这些数据,从而导致未定义的行为。

foo&
foo::operator=(foo const& rhs)
{
   using std::swap;
   foo tmp(rhs);
   swap(*this, tmp);
   return *this;
}
Run Code Online (Sandbox Code Playgroud)

  • @juanchopanza 即使您实际上并不需要强有力的保证,交换习惯通常也是获得最低保证的最简单且最便宜的方法。 (2认同)