从复制构造函数调用默认赋值运算符是一种不好的形式吗?

发布于 2024-08-07 22:13:09 字数 1173 浏览 10 评论 0原文

考虑一个需要制作副本的类。副本中的绝大多数数据元素必须严格反映原始数据元素,但是有少数元素的状态不需要保留并需要重新初始化

从复制构造函数调用默认赋值运算符是一种不好的形式吗?

默认的赋值运算符对于普通旧数据(int,double,char,short)以及每个赋值运算符的用户定义的类都表现良好。指针需要单独处理。

一个缺点是,由于未执行额外的重新初始化,因此该方法会导致赋值运算符瘫痪。也不可能禁用赋值运算符的使用,从而为用户提供了通过使用不完整的默认赋值运算符 A obj1,obj2; 创建损坏的类的选项。对象2=对象1; /* 结果可能是一个错误初始化的 obj2 */

除了 a(0),b(0) .. 之外,最好放宽对 a(orig.a),b(orig.b)... 的要求。 . 必须写。需要将所有初始化写入两次会产生两个错误位置,并且如果要将新变量(例如 double x,y,z)添加到类中,则需要在以下位置正确添加初始化代码至少 2 个位置而不是 1 个。

有更好的方法吗?

C++0x 中有更好的方法吗?

class A {
  public:
    A(): a(0),b(0),c(0),d(0)
    A(const A & orig){
      *this = orig;       /* <----- is this "bad"? */
      c = int();
    }
  public:
    int a,b,c,d;
};

A X;
X.a = 123;
X.b = 456;
X.c = 789;
X.d = 987;

A Y(X);

printf("X: %d %d %d %d\n",X.a,X.b,X.c,X.d);
printf("Y: %d %d %d %d\n",Y.a,Y.b,Y.c,Y.d);

输出:

X: 123 456 789 987
Y: 123 456 0 987

替代复制构造函数:

A(const A & orig):a(orig.a),b(orig.b),c(0),d(orig.d){}  /* <-- is this "better"? */

Consider a class of which copies need to be made. The vast majority of the data elements in the copy must strictly reflect the original, however there are select few elements whose state is not to be preserved and need to be reinitialized.

Is it bad form to call a default assignment operator from the copy constructor?

The default assignment operator will behave well with Plain Old Data( int,double,char,short) as well user defined classes per their assignment operators. Pointers would need to be treated separately.

One drawback is that this method renders the assignment operator crippled since the extra reinitialization is not performed. It is also not possible to disable the use of the assignment operator thus opening up the option of the user to create a broken class by using the incomplete default assignment operator A obj1,obj2; obj2=obj1; /* Could result is an incorrectly initialized obj2 */ .

It would be good to relax the requirement that to a(orig.a),b(orig.b)... in addition to a(0),b(0) ... must be written. Needing to write all of the initialization twice creates two places for errors and if new variables (say double x,y,z) were to be added to the class, initialization code would need to correctly added in at least 2 places instead of 1.

Is there a better way?

Is there be a better way in C++0x?

class A {
  public:
    A(): a(0),b(0),c(0),d(0)
    A(const A & orig){
      *this = orig;       /* <----- is this "bad"? */
      c = int();
    }
  public:
    int a,b,c,d;
};

A X;
X.a = 123;
X.b = 456;
X.c = 789;
X.d = 987;

A Y(X);

printf("X: %d %d %d %d\n",X.a,X.b,X.c,X.d);
printf("Y: %d %d %d %d\n",Y.a,Y.b,Y.c,Y.d);

Output:

X: 123 456 789 987
Y: 123 456 0 987

Alternative Copy Constructor:

A(const A & orig):a(orig.a),b(orig.b),c(0),d(orig.d){}  /* <-- is this "better"? */

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

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

发布评论

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

评论(6

策马西风 2024-08-14 22:13:09

正如brone 指出的那样,您最好在副本构造方面实施分配。我更喜欢他的另一种习惯用法:

T& T::operator=(T t) {
    swap(*this, t);
    return *this;
}

它有点短,并且 可以利用一些深奥的语言特性来提高性能。与任何优秀的 C++ 代码一样,它也有一些需要注意的微妙之处。

首先,t 参数有意按值传递,以便调用复制构造函数 (大多数时候),我们可以根据自己的喜好修改它,而不影响原始值。使用 const T& 将无法编译,并且 T& 会通过修改分配来源值来触发一些令人惊讶的行为。

此技术还要求 swap 以不使用类型的赋值运算符的方式专门用于该类型(如 std::swap 所做的那样),否则会导致无限递归。请小心任何杂散的 using std::swapusing namespace std,因为它们会将 std::swap 拉入范围并在以下情况下导致问题:您没有将 swap 专门用于 T。重载解析和 ADL 将确保使用正确的交换版本(如果您已定义)。

有几种方法可以为类型定义交换。第一个方法使用 swap 成员函数来完成实际工作,并具有委托给它的 swap 专门化,如下所示:

class T {
public:
    // ....
    void swap(T&) { ... }
};

void swap(T& a, T& b) { a.swap(b); }

这在标准库中很常见;例如,std::vector 就以这种方式实现了交换。如果您有一个 swap 成员函数,您可以直接从赋值运算符调用它,并避免函数查找时出现任何问题。

另一种方法是将 swap 声明为友元函数并让它完成所有工作:

class T {
    // ....
    friend void swap(T& a, T& b);
};

void swap(T& a, T& b) { ... }

我更喜欢第二种,因为 swap() 通常不是一个组成部分类的接口;作为免费功能似乎更合适。然而,这是一个品味问题。

对类型进行优化的交换是实现 C++0x 中右值引用的一些好处的常用方法,因此,如果类可以利用它并且您可以这样做,那么一般来说这是一个好主意确实需要表演。

As brone points out, you're better off implementing assignment in terms of copy construction. I prefer an alternative idiom to his:

T& T::operator=(T t) {
    swap(*this, t);
    return *this;
}

It's a bit shorter, and can take advantage of some esoteric language features to improve performance. Like any good piece of C++ code, it also has some subtleties to watch for.

First, the t parameter is intentionally passed by value, so that the copy constructor will be called (most of the time) and we can modify is to our heart's content without affecting the original value. Using const T& would fail to compile, and T& would trigger some surprising behaviour by modifying the assigned-from value.

This technique also requires swap to be specialized for the type in a way that doesn't use the type's assignment operator (as std::swap does), or it will cause an infinite recursion. Be careful of any stray using std::swap or using namespace std, as they will pull std::swap into scope and cause problems if you didn't specialize swap for T. Overload resolution and ADL will ensure the correct version of swap is used if you have defined it.

There are a couple of ways to define swap for a type. The first method uses a swap member function to do the actual work and has a swap specialization that delegates to it, like so:

class T {
public:
    // ....
    void swap(T&) { ... }
};

void swap(T& a, T& b) { a.swap(b); }

This is pretty common in the standard library; std::vector, for example, has swapping implemented this way. If you have a swap member function you can just call it directly from the assignment operator and avoid any issues with function lookup.

Another way is to declare swap as a friend function and have it do all of the work:

class T {
    // ....
    friend void swap(T& a, T& b);
};

void swap(T& a, T& b) { ... }

I prefer the second one, as swap() usually isn't an integral part of the class' interface; it seems more appropriate as a free function. It's a matter of taste, however.

Having an optimized swap for a type is a common method of achieving some of the benefits of rvalue references in C++0x, so it's a good idea in general if the class can take advantage of it and you really need the performance.

幻梦 2024-08-14 22:13:09

使用您的复制构造函数版本,首先默认构造成员,然后分配成员。
对于整型,这并不重要,但如果您有像 std::string 这样的重要成员,那么这就是不必要的开销。

因此,是的,一般来说,您的替代复制构造函数更好,但如果您只有整型类型作为成员,那并不重要。

With your version of the copy constructor the members are first default-constructed and then assigned.
With integral types this doesn't matter, but if you had non-trivial members like std::strings this is unneccessary overhead.

Thus, yes, in general your alternative copy constructor is better, but if you only have integral types as members it doesn't really matter.

半暖夏伤 2024-08-14 22:13:09

本质上,你所说的是你的班级中有一些成员对班级的身份没有贡献。就目前情况而言,您可以通过使用赋值运算符复制类成员,然后重置那些不应复制的成员来表达这一点。这会留下一个与复制构造函数不一致的赋值运算符。

更好的方法是使用复制和交换习惯用法,并表达哪些成员不应在复制构造函数中复制。您仍然有一处表达“不要复制此成员”行为,但现在您的赋值运算符和复制构造函数是一致的。

class A
{
public:

    A() : a(), b(), c(), d() {}

    A(const A& other)
        : a(other.a)
        , b(other.b)
        , c() // c isn't copied!
        , d(other.d)

    A& operator=(const A& other)
    {
        A tmp(other); // doesn't copy other.c
        swap(tmp);
        return *this;
    }

    void Swap(A& other)
    {
        using std::swap;
        swap(a, other.a);
        swap(b, other.b);
        swap(c, other.c); // see note
        swap(d, other.d);
    }

private:
    // ...
};

注意:swap成员函数中,我交换了c成员。为了在赋值运算符中使用,这保留了与复制构造函数相匹配的行为:它重新初始化 c 成员。如果您将 swap 函数保留为公共,或者通过 swap 免费函数提供对其的访问,则应确保此行为适合交换的其他用途。

Essentially, what you are saying is that you have some members of your class which don't contribute to the identity of the class. As it currently stands you have this expressed by using the assignment operator to copy class members and then resetting those members which shouldn't be copied. This leaves you with an assignment operator that is inconsistent with the copy constructor.

Much better would be to use the copy and swap idiom, and express which members shouldn't be copied in the copy constructor. You still have one place where the "don't copy this member" behaviour is expressed, but now your assignment operator and copy constructor are consistent.

class A
{
public:

    A() : a(), b(), c(), d() {}

    A(const A& other)
        : a(other.a)
        , b(other.b)
        , c() // c isn't copied!
        , d(other.d)

    A& operator=(const A& other)
    {
        A tmp(other); // doesn't copy other.c
        swap(tmp);
        return *this;
    }

    void Swap(A& other)
    {
        using std::swap;
        swap(a, other.a);
        swap(b, other.b);
        swap(c, other.c); // see note
        swap(d, other.d);
    }

private:
    // ...
};

Note: in the swap member function, I have swapped the c member. For the purposes of use in the assignment operator this preserves the behaviour to match that of the copy constructor: it re-initializes the c member. If you leave the swap function public, or provide access to it through a swap free function you should make sure that this behaviour is suitable for other uses of swap.

半葬歌 2024-08-14 22:13:09

我个人认为损坏的赋值运算符是杀手。我总是说人们应该阅读文档,而不是做任何它告诉他们不要做的事情,但即便如此,在不考虑它的情况下编写作业还是太容易了,或者使用需要类型可分配的模板。不可复制的习惯用法是有原因的:如果 operator= 不起作用,那么让它可访问就太危险了。

如果我没记错的话,C++0x 会让你这样做:

private:
    A &operator=(const A &) = default;

那么至少只有类本身可以使用损坏的默认赋值运算符,并且你希望在这个受限上下文中更容易小心。

Personally I think the broken assignment operator is killer. I always say that people should read the documentation and not do anything it tells them not to, but even so it's just too easy to write an assignment without thinking about it, or use a template which requires the type to be assignable. There's a reason for the noncopyable idiom: if operator= isn't going to work, it's just too dangerous to leave it accessible.

If I remember rightly, C++0x will let you do this:

private:
    A &operator=(const A &) = default;

Then at least it's only the class itself which can use the broken default assignment operator, and you'd hope that in this restricted context it's easier to be careful.

拥抱影子 2024-08-14 22:13:09

我将其称为不好的形式,不是因为您对所有对象进行了双重分配,而是因为根据我的经验,依赖默认的复制构造函数/赋值运算符来实现一组特定的功能通常是不好的形式。由于这些不在任何地方的源代码中,因此很难判断您想要的行为取决于它们的行为。例如,如果一年内有人想要向您的类添加一个字符串向量怎么办?您不再拥有普通的旧数据类型,维护人员很难知道他们正在破坏事物。

我认为,尽管 DRY 很好,但从维护的角度来看,创建微妙的未指定的需求要糟糕得多。即使重复自己的话,虽然很糟糕,但也没有那么邪恶。

I would call it bad form, not because you double-assign all your objects, but because in my experience it's often bad form to rely on the default copy constructor / assignment operator for a specific set of functionality. Since these are not in the source anywhere, it's hard to tell that the behavior you want depends on their behavior. For instance, what if someone in a year wants to add a vector of strings to your class? You no longer have the plain old datatypes, and it would be very hard for a maintainer to know that they were breaking things.

I think that, nice as DRY is, creating subtle un-specified requirements is much worse from a maintenance point of view. Even repeating yourself, as bad as that is, is less evil.

欲拥i 2024-08-14 22:13:09

我认为更好的方法是如果行为微不足道,则不实现复制构造函数(在您的情况下,它似乎被破坏了:至少赋值和复制应该具有相似的语义,但您的代码表明这不会)事实并非如此——但我认为这是一个人为的例子)。为您生成的代码不可能是错误的。

如果您需要实现这些方法,该类很可能可以使用快速交换方法,从而能够重用复制构造函数来实现赋值运算符。

如果您出于某种原因需要提供默认的浅复制构造函数,那么 C++0X 有

 X(const X&) = default;

但我不认为有奇怪的语义的习惯用法。在这种情况下,使用赋值而不是初始化是很便宜的(因为保持 int 未初始化不会花费任何成本),所以你也可以这样做。

I think the better way is not to implement a copy constructor if the behaviour is trivial (in your case it appears to be broken: at least assignment and copying should have similar semantics but your code suggests this won't be so - but then I suppose it is a contrived example). Code that is generated for you cannot be wrong.

If you need to implement those methods, most likely the class could do with a fast swap method and thus be able to reuse the copy constructor to implement the assignment operator.

If you for some reason need to provide a default shallow copy constructor, then C++0X has

 X(const X&) = default;

But I don't think there is an idiom for weird semantics. In this case using assignment instead of initialization is cheap (since leaving ints uninitialized doesn't cost anything), so you might just as well do it like this.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文