12

出于某种原因,我不得不为我的类同时提供一个复制构造函数和一个 operator=。operator=如果我定义了一个复制 ctor,我想我不需要,但QList想要一个。撇开这一点不谈,我讨厌代码重复,那么这样做有什么问题吗?

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

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


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

4

6 回答 6

26

这很糟糕,因为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);
}

这就是我通常编写赋值运算符的方式。阅读想要速度?关于异常赋值运算符签名的按值传递(按值传递)

于 2009-09-22T02:33:51.687 回答
8

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

于 2009-09-22T02:29:04.647 回答
6

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

于 2009-09-22T07:14:17.990 回答
3

如果您的 operator= 变成虚拟的,我认为您会遇到问题。

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

于 2009-09-22T02:30:37.140 回答
2

是的,这是一种很好的做法,应该(几乎)总是这样做。除了折腾一个析构函数和默认构造函数(即使你把它设为私有)。

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

一般来说,如果出现以下情况,您必须使用正统的规范形式:

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

应该对程序中的任何非平凡类使用正统的规范形式,以便在类之间保持统一,并在程序演变过程中管理每个类日益增加的复杂性。

Coplien 为这种模式提供了数页的理由,我在这里无法做到公正。但是,已经触及的一个关键项目是清理被覆盖对象的能力。

于 2009-09-22T02:51:04.300 回答
1

我认为您应该使用initializer list. 如果你的变量是primitive-types,那么没关系。否则,赋值不同于初始化。


copy constructor你可以通过在to中初始化指针来做一个小技巧0,然后你可以在里面安全地调用 delete 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;
}
于 2009-09-22T02:28:48.890 回答