简体   繁体   English

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

[英]Avoid repeating the same code in copy constructor and operator=

In c++ when classes contains dynamically allocated data it is usually reasonable to explicitly define copy constructor, operator= and destructor.在 C++ 中,当类包含动态分配的数据时,显式定义复制构造函数、operator= 和析构函数通常是合理的。 But the activity of these special methods overlaps.但是这些特殊方法的活动是重叠的。 More specifically operator= usually first does some destruction and then it does coping similar to the one in copy constructor.更具体地说, operator= 通常首先进行一些破坏,然后进行类似于复制构造函数中的处理。

My question is how to write this the best way without repeating the same lines of code and without the need for processor to do unnecessary work (like unnecessary copying).我的问题是如何在不重复相同代码行且不需要处理器做不必要的工作(如不必要的复制)的情况下以最佳方式编写此代码。

I usually end up with two helping methods.我通常以两种帮助方法结束。 One for construction and one for destruction.一种用于建造,一种用于破坏。 The first is called from both copy constructor and operator=.第一个是从复制构造函数和 operator= 调用的。 The second is used by destructor and operator=.第二个由析构函数和 operator= 使用。

Here is the example code:这是示例代码:

    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();
        }
    };

Is this even correct?这甚至正确吗? And is it a good habit to split the code this way?而且这样拆分代码是不是一个好习惯?

One initial comment: the operator= does not start by destructing, but by constructing.一个最初的评论: operator=不是从破坏开始,而是从构造开始。 Otherwise, it will leave the object in an invalid state if the construction terminates via an exception.否则,如果构造通过异常终止,它将使对象处于无效状态。 Your code is incorrect because of this.因此,您的代码不正确。 (Note that the necessity to test for self assignment is usually a sign that the assignment operator is not correct.) (请注意,测试自赋值的必要性通常表明赋值运算符正确。)

The classical solution for handling this is the swap idiom: you add a member function swap:处理这个问题的经典解决方案是交换习语:你添加一个成员函数 swap:

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

which is guaranteed not to throw.保证不会抛出。 (Here, it just swaps an int and a pointer, neither of which can throw.) Then you implement the assignment operator as: (这里,它只是交换一个 int 和一个指针,两者都不能抛出。)然后你将赋值运算符实现为:

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

This is simple and straight forward, but any solution in which all operations which may fail are finished before you start changing the data is acceptable.这是简单而直接的,但是在您开始更改数据之前完成所有可能失败的操作的任何解决方案都是可以接受的。 For a simple case like your code, for example:对于像您的代码这样的简单案例,例如:

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

(where cloneData is a member function which does most of what your construct does, but returns the pointer, and doesn't modify anything in this ). (其中cloneData是一个成员函数,它执行您的construct所做的大部分工作,但返回指针,并且不修改this任何内容)。

EDIT:编辑:

Not directly related to your initial question, but generally, in such cases, you do not want to do a new T[count] in cloneData (or construct , or whatever).不直接关系到你最初的问题,但总体上,在这种情况下,你不想做一个new T[count]cloneData (或construct ,或其他)。 This constructs all of the T with the default constructor, and then assigns them.这将使用默认构造函数构造所有T ,然后分配它们。 The idiomatic way of doing this is something like:这样做的惯用方式是这样的:

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;
}

Most often, this will be done using a separate (private) manager class:大多数情况下,这将使用单独的(私有)管理器类来完成:

//  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;
    }
}

(and of course, the swap idiom for assignment). (当然,还有用于赋值的交换习语)。 This allows multiple count/data pairs without any risk of loosing exception safety.这允许多个计数/数据对,而没有任何失去异常安全的风险。

Implement the assignment by first copying the right-hand side and then swapping with that.通过首先复制右侧然后与之交换来实现分配。 This way you also get exception safety, which your code above doesn't provide.通过这种方式,您还可以获得异常安全,而上面的代码没有提供。 You could end up with a broken container when construct() fails after destruct() succeeded otherwise, because the member pointer references some deallocated data, and on destruction that will be deallocated again, causing undefined behaviour.否则,在 destruct() 成功之后,construct() 失败时,您可能最终会得到一个损坏的容器,因为成员指针引用了一些已释放的数据,并且在销毁时将再次释放,导致未定义的行为。

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

I don't see any inherent problem with that, as long as you make sure not to declare construct or destruct virtual.我没有看到任何固有的问题,只要你确保不声明构造或破坏虚拟。

You might be interested in chapter 2 in Effective C++ (Scott Meyers), which is completely devoted to constructors, copy operators and destructors.您可能对 Effective C++ (Scott Meyers) 中的第 2 章感兴趣,该章完全致力于构造函数、复制运算符和析构函数。

As for exceptions, which your code is not handling as it should, consider items 10 & 11 in More effective C++ (Scott Meyers).至于您的代码未按应有方式处理的异常,请考虑更有效的 C++ (Scott Meyers) 中的第 10 项和第 11 项。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM