避免在复制构造函数和运算符=中重复相同的代码
Avoid repeating the same code in copy constructor and operator=
在c++中,当类包含动态分配的数据时,显式定义复制构造函数、运算符=和析构函数通常是合理的。但这些特殊方法的活动是重叠的。更具体地说,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();
}
};
这是正确的吗?这样划分代码是个好习惯吗?
一个初始注释:operator=
不通过破坏,但通过建设。否则,它将离开如果构造通过例外因此,您的代码不正确。(注意自我分配测试的必要性通常表明赋值运算符不正确。)
处理此问题的经典解决方案是交换习惯用法:添加成员功能交换:
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;
}
(其中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;
}
大多数情况下,这将使用单独的(私人)管理器来完成类别:
// 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;
}
}
(当然,还有任务的交换习语)。这允许多个计数/数据对,没有任何丢失异常的风险安全
我认为这没有任何固有的问题,只要你确保不声明construct或destruct virtual。
您可能对Effective C++(Scott Meyers)的第2章感兴趣,该章完全致力于构造函数、复制运算符和析构函数。
至于异常,您的代码没有按应有的方式处理,请考虑第10&11在更有效的C++(Scott Meyers)中。
通过首先复制右侧,然后与其交换来实现赋值。通过这种方式,您还可以获得上面的代码所没有提供的异常安全性。如果construct()失败,则可能会导致容器损坏。否则,因为成员指针引用了一些已释放的数据,而在销毁时,这些数据将再次被释放,从而导致未定义的行为。
foo&
foo::operator=(foo const& rhs)
{
using std::swap;
foo tmp(rhs);
swap(*this, tmp);
return *this;
}
- 以下示例中如何避免代码复制?C++/库达
- 如何使用从C++代码写入时复制 BTRFS?
- 如果 iostream 对象不可复制,为什么以下代码是合法的?
- 为什么我的代码在尝试复制字符数组时引发 C6386 错误?
- 是否可以避免在以下代码中复制/移动构造函数的需要?
- 为什么在我的代码中调用复制构造函数而不是移动构造函数?
- 为什么在使用转换构造函数编译代码时需要 const 复制构造函数?
- C++具有移动和复制构造函数的类中的代码重复
- 如何在不复制此代码的情况下将多个函数放入多个命名空间?
- 表示行为与复制代码块的函数参数?
- 避免使用 auto 关键字从字面上复制 const 和非 const 的代码?
- 为什么在下面的代码中调用复制构造函数两次
- 从教科书中一对一复制的代码绝对没有任何作用
- 复制赋值和复制构造函数(代码C++的差异)
- 是否可以动态翻译QT应用程序而无需复制代码
- 我无法使用 c++(代码块)中的 fstream 将文件内容复制到另一个。如何运行该文件?
- 循环设计:计数和后续代码复制
- C++将格式化的代码复制到单词(如视觉助手)
- 将代码复制到RAM-如何一次复制多个功能
- 是否有任何方法可以避免跨类的不同构造函数进行代码复制