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

发布于 2024-08-05 07:30:55 字数 691 浏览 8 评论 0原文

由于某种原因,我被迫为我的类提供复制构造函数和运算符=。我认为如果我定义了一个复制构造函数,我就不需要 operator= ,但是 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;
}

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


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

For one reason or another, I'm forced to provide both a copy constructor and an operator= for my class. I thought I didn't need operator= if I defined a copy ctor, but QList wants one. Putting that aside, I hate code duplication, so is there anything wrong with doing it this way?

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

And just out of curiosity, there's no way to switch it so that the bulk of the code is in the copy ctor and operator= somehow utilizes it? I tried return Fixture(f); but it didn't like that.


It appears I need to make it more clear that the copy constructor and assignment operator have been implicitly disabled by the class I am inheriting from. Why? Because it's an abstract base class that shouldn't be instantiated on its own. This class, however, is meant to stand alone.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

无人问我粥可暖 2024-08-12 07:30:55

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

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

This is bad, because the operator= can't rely on a set-up object anymore. You should do it the other way around, and can use the copy-swap idiom.

In the case where you just have to copy over all elements, you can use the implicitly generated assignment operator.

In other cases, you will have to do something in addition, mostly freeing and copying memory. This is where the copy-swap idiom is good for. Not only is it elegant, but it also provide so an assignment doesn't throw exceptions if it only swaps primitives. Let's a class pointing to a buffer that you need to copy:

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

That's how i write the assignment operator usually. Read Want speed? Pass by value about the unusual assignment operator signature (pass by value).

忆沫 2024-08-12 07:30:55

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

Copy ctor and assignment are entirely distinct -- assignment typically needs to free resources in the object that it's replacing, copy ctor is working on a not-yet-initialized object. Since here you apparently have no special requirements (no "releasing" needed on assignment), your approach is fine. More in general, you might have a "free all resources the object is holding" auxiliary method (to be called in the dtor and at the start of assignment) as well as the "copy these other things into the object" part that's reasonably close to the work of a typical copy ctor (or mostly, anyway;-).

满地尘埃落定 2024-08-12 07:30:55

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

You're simply doing a member-wise copy and assignment in your examples. This is not something you need to write yourself. The compiler can generate implicit copy and assignment operations that do exactly that. You only need to write your own copy constructor, assignment and/or destructor if the compiler-generated ones are not appropriate (i.e. in case you manage some resource through a pointer or something like that)

ㄟ。诗瑗 2024-08-12 07:30:55

我认为如果您的操作员=变得虚拟,您会遇到问题。

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

I think you run into issues if your operator= ever becomes virtual.

I would recommend writing a function (maybe static) that does the copy then have the copy-constructor and operator= call that function instead.

丿*梦醉红颜 2024-08-12 07:30:55

是的,这是一个很好的做法,应该(几乎)始终这样做。此外,还添加了析构函数和默认构造函数(即使您将其设为私有)。

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

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

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

应该对程序中的任何重要类使用正统规范形式,以实现类之间的一致性并管理每个类在程序演化过程中不断增加的复杂性。

Coplien 提供了很多关于这种模式的原因,但我无法在这里公正地解释它们。然而,已经涉及的一个关键项目是清理被覆盖的对象的能力。

Yes, this is good practice and should (almost) always be done. In addition toss in a destructor and default constructor (even if you make it private).

In James Coplien's 1991 book Advanced C++, this is described as part of "Orthodox Canonical Form". In it, he advocates for a default constructor, a copy constructor, the assignment operator and a destructor.

In general, you must use the orthodox canonical form if:

  • You want to support assignment of object of the class, or want to pass those objects as call-by-value parameters to a function, and
  • The object contains pointers to objects that are reference-counted, or the class destructor performs a delete on a data member of the object.

You should use the orthodox canonical form for any nontrivial class in a program, for the sake of uniformity across classes and to manage the increasing complexity of each class over the course of program evolution.

Coplien offers pages of reasons for this pattern and I couldn't do them justice here. However, a key item that has already been touched on is the ability to clean up the object that is being overwritten.

方觉久 2024-08-12 07:30:55

我认为你应该使用初始化列表来初始化你的对象成员变量。如果您的变量是原始类型,那么这并不重要。否则,赋值与初始化不同。


您可以通过将复制构造函数内的指针初始化为0来实现这一点,然后您可以在赋值运算符中安全地调用delete:

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

I think you should initialize your object member variables using initializer list. If your variables are of primitive-types, then it doesn't matter. Otherwise, assignment is different from initialization.


You could do it with a little trick by initializing pointers inside the copy constructor to 0, then you could call delete safely in the 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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文