这是好的代码吗? (复制构造函数和赋值运算符)

时间:2009-09-22 02:20:33

标签: c++ operator-overloading copy-constructor

由于某种原因,我不得不为我的班级提供复制构造函数和operator =。如果我定义了一个副本ctor,我认为我不需要operator=,但是QList想要一个。{1}}。把它放在一边,我讨厌代码重复,所以这样做有什么不妥吗?

Fixture::Fixture(const Fixture& f) {
    *this = f;
}

Fixture& Fixture::operator=(const Fixture& f) {
    m_shape         = f.m_shape;
    m_friction      = f.m_friction;
    m_restitution   = f.m_restitution;
    m_density       = f.m_density;
    m_isSensor      = f.m_isSensor;
    return *this;
}

出于好奇,没有办法切换它,以便大部分代码都在复制文件中,而operator=以某种方式利用它?我试过return Fixture(f);,但不喜欢这样。


看来我需要更清楚地说明复制构造函数和赋值运算符已被我继承的类隐式禁用。为什么?因为它是一个抽象的基类,不应该自己实例化。但是,这个类 意味着独立。

6 个答案:

答案 0 :(得分:25)

这很糟糕,因为operator=不再依赖于设置对象。你应该反过来做,并可以使用复制交换习语。

如果您只需复制所有元素,则可以使用隐式生成的赋值运算符。

在其他情况下,您必须另外做一些事情,主要是释放和复制内存。这就是复制交换习惯用法的好处。它不仅优雅,而且还提供如此分配,如果只交换原语,则不会抛出异常。我们是一个指向您需要复制的缓冲区的类:

Fixture::Fixture():m_data(), m_size() { }

Fixture::Fixture(const Fixture& f) {
    m_data = new item[f.size()];
    m_size = f.size();
    std::copy(f.data(), f.data() + f.size(), m_data);
}

Fixture::~Fixture() { delete[] m_data; }

// note: the parameter is already the copy we would
// need to create anyway. 
Fixture& Fixture::operator=(Fixture f) {
    this->swap(f);
    return *this;
}

// efficient swap - exchanging pointers. 
void Fixture::swap(Fixture &f) {
    using std::swap;
    swap(m_data, f.m_data);
    swap(m_size, f.m_size);
}

// keep this in Fixture's namespace. Code doing swap(a, b)
// on two Fixtures will end up calling it. 
void swap(Fixture &a, Fixture &b) {
  a.swap(b);
}

这就是我通常编写赋值运算符的方式。阅读有关异常分配操作员签名的Want speed? Pass by value(按值传递)。

答案 1 :(得分:8)

复制ctor和赋值完全不同 - 赋值通常需要释放它所替换的对象中的资源,复制ctor正在处理一个尚未初始化的对象。从那以后你显然没有特殊要求(转让时不需要“释放”),你的方法很好。更一般地说,你可能有一个“对象所持有的所有资源”辅助方法(在dtor和分配开始时调用)以及“将这些其他东西复制到对象中”这一部分相当接近对于一个典型的复制文件的工作(或者主要是,无论如何; - )。

答案 2 :(得分:6)

您只是在示例中执行成员明确的复制和分配。这不是你自己需要写的东西。编译器可以生成完全相同的隐式复制和赋值操作。如果编译器生成的那些不合适(例如,如果你通过指针或类似的东西管理某些资源),你只需要编写自己的拷贝构造函数,赋值和/或析构函数。

答案 3 :(得分:3)

如果您的运营商=变得虚拟,我认为您会遇到问题。

我建议编写一个函数(可能是静态的)来复制,然后复制构造函数和operator =调用该函数。

答案 4 :(得分:2)

是的,这是一种很好的做法,应该(几乎)总是这样做。另外在析构函数和默认构造函数中抛出(即使你将其设为私有)。

在James Coplien的1991年书Advanced C++中,这被描述为“正统规范形式”的一部分。在其中,他提倡默认构造函数,复制构造函数,赋值运算符和析构函数。

  

一般情况下,必须使用正统的规范形式,如果:

     
      
  • 您希望支持对象的对象分配,或者希望将这些对象作为按值调用参数传递给函数
  •   
  • 该对象包含指向引用计数的对象的指针,或者类析构函数对该对象的数据成员执行delete
  •   
     

p>

Coplien提供了这种模式的原因页面,我无法在这里做到正义。但是,已经触及的关键项是能够清理被覆盖的对象。

答案 5 :(得分:1)

我认为您应该使用initializer list初始化对象成员变量。如果您的变量是primitive-types,那么无关紧要。否则,赋值与初始化不同。


你可以通过初始化copy constructor0内的指针来做一个小技巧,然后你可以在assignment operator中安全地调用删除:

Fixture::Fixture(const Fixture& f) : myptr(0) {
    *this = f;
}
Fixture& Fixture::operator=(const Fixture& f) {
    // if you have a dynamic array for example, delete other wise.
    delete[] myptr;
    myptr = new int[10];
    // initialize your array from the other object here.
    ......
    return *this;
}