4

This is not a duplicate of Implementing the copy constructor in terms of operator= but is a more specific question. (Or so I like to think.)

Intro

Given a (hypothetical) class like this:

struct FooBar {
  long id;
  double valX;
  double valZ;
  long   valN;
  bool   flag; 
  NonCopyable implementation_detail; // cannot and must not be copied

  // ...
};

we cannot copy this by the default generated functions, because you can neither copy construct nor copy a NonCopyable object. However, this part of the object is an implementation detail we are actually not interested in copying.

It does also does not make any sense to write a swap function for this, because the swap function could just replicate what std::swap does (minus the NonCopyable).

So if we want to copy these objects, we are left with implementing the copy-ctor and copy-operator ourselves. This is trivially done by just assigning the other members.

Question

If we need to implement copy ctor and operator, should we implement the copy ctor in terms of the copy operator, or should we "duplicate" the code with initialization list?

That is, given:

FooBar& operator=(FooBar const& rhs) {
  // no self assignment check necessary
  id = rhs.id;
  valX = rhs.valX;
  valZ = rhs.valZ;
  valN = rhs.valN;
  flag = rhs.flag;
  // don't copy implementation_detail
  return *this;
}

Should we write a)

FooBar(FooBar const& rhs) {
  *this = rhs;
}

or b)

FooBar(FooBar const& rhs)
: id(rhs.id)
, valX(rhs.valX)
, valZ(rhs.valZ)
, valN(rhs.valN)
, flag(rhs.flag)
// don't copy implementation_detail
{ }

Possible aspects for an answer would be performance vs. maintainability vs. readability.

Community
  • 1
  • 1
Martin Ba
  • 37,187
  • 33
  • 183
  • 337
  • Note: the "correct" terms are Copy Constructor and Copy Assignment Operator (or just Assignment Operator pre-C++0x). – Matthieu M. Nov 08 '10 at 08:05
  • If the implementation_detail has state, the swap can't be made to work correctly. If the implementation_detail does not have state, why not make it static? – Sjoerd Nov 08 '10 at 08:12
  • I usually prefer to make classes non-copyable. Or wrap the uncopyable part in a copyable way (shared_ptr or whatever). I would do anything to benefit from the default generated functions, as I don't want to have the burden to update the copy constructor every time I add a member variable. – Sjoerd Nov 08 '10 at 11:46

5 Answers5

5

Normally you implement assignment operator in terms of copy constructor (@Roger Pate's version):

FooBar& operator=(FooBar copy) { swap(*this, copy); return *this; }
friend void swap(FooBar &a, FooBar &b) {/*...*/}

This requires providing a swap function which swaps relevant members (all except implementation_detail in your case).

If swap doesn't throw this approach guarantees that object is not left in some inconsistent state (with only part members assigned).

However in your case since neither copy constructor, nor assignment operator can throw implementing copy constructor in terms of assignment operator (a) is also fine and is more maintainable then having almost identical code in both places (b).

vitaut
  • 49,672
  • 25
  • 199
  • 336
  • 2
    `FooBar& operator=(FooBar copy) { swap(*this, copy); return *this; } friend void swap(FooBar &a, FooBar &b) {/*...*/}` –  Nov 08 '10 at 07:50
  • The question is specifically about (a) or (b) and *not* about swap as implementing swap somehow doesn't make any sense in the given example. – Martin Ba Nov 08 '10 at 08:01
  • @vitaut: there is normally no reason for `swap` to throw, in fact many algorithms expect it NOT to throw to maintain their exception guarantees. – Matthieu M. Nov 08 '10 at 08:07
  • 2
    @Martin if implementing swap doesn't make sense, why do you think it makes sense to implement an assignment operator? – Sjoerd Nov 08 '10 at 08:42
  • @Martin: Implementing a no-throw `swap` is a simple step into easy exception safe code. It can be used in different contexts. It is quite desirable in many cases to provide the `swap` operation regardless of this particular question, and once it is in place, then using it is just the simplest thing you can do. On the other hand, there are a few cases where implementing a no-throw `swap` might be non-trivial and where you can live without the strong exception guarantee... – David Rodríguez - dribeas Nov 08 '10 at 08:58
  • @Martin: Sjoerd asked a good question. I don't see how could assignment make sense and swap don't. – vitaut Nov 08 '10 at 09:05
  • @vitaud : I meant to say implementing swap doesn't make sense because it does not add any value to implement the swap function for such a pure value class, as std::swap is already as efficient as it gets. – Martin Ba Nov 08 '10 at 11:27
  • @Martin: In this case `swap` is used for exception safety, not to provide a more efficient alternative to `std::swap`. However, as I already mentioned in your case it is not necessary, since all the members that you copy are primitive and therefore neither copy constructor, nor assignment operator can throw (although the situation may change if you add members to the class). – vitaut Nov 08 '10 at 11:37
1

In general, I prefer b) over a) as it explicitly avoids any default construction of members. For ints, doubles etc. that isn't a consideration, but it can be for members with expensive operations or side effects. It's more maintainable if you don't have to consider this potential cost/issue as you're adding and removing members. Initialiser lists also support references and non-default-constructable elements.

Alternatively, you could have a sub-structure for the non-"implementation detail" members and let the compiler generate copying code, along the lines:

struct X 
{
    struct I
    {
        int x_;
        int y_;
    } i_;
    int z_;

    X() { }

    X(const X& rhs)
      : i_(rhs.i_), z_(0) // implementation not copied
    { }

    X& operator=(const X& rhs)
    {
        i_ = rhs.i_;
        return *this;
    } 
};
Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
1

If you're really bothered about replicating std::swap, why not put everything other than the implementation detail into a struct?

struct FooBarCore {
  long id;
  double valX;
  double valZ;
  long   valN;
  bool   flag; 
  // ...
};

struct FooBar {
  FooBarCore core_;
  NonCopyable implementation_detail; // cannot and must not be copied
};

then you can use std::swap for this struct in your copy function for FooBar.

FooBar& operator=(const FooBar &src) {
  FooBarCore temp(src.core_)
  swap(temp,*this.core_);
  return *this;
}
beldaz
  • 4,299
  • 3
  • 43
  • 63
  • Another option would be to wrap the `implementation_detail` into a class with an empty Copy Constructor and empty Copy Assignment Operator, and use that (copyable) class in FooBar. – Sjoerd Nov 08 '10 at 11:24
1

Okay, another try, based on my comment to this answer.

Wrap the implementation_detail in a copyable class:

class ImplementationDetail
{
public:
  ImplementationDetail() {}
  ImplementationDetail(const ImplementationDetail&) {}
  ImplementationDetail& operator=(const ImplementationDetail&) {}

public: // To make the example short
  Uncopyable implementation_detail;
};

and use this class in your FooBar. The default generated Copy Constructor and Copy Assignment Operator for Foobar will work correctly.

Maybe it could even derive from Uncopyable so you don't get implementation_detail.implementation_detail all over your code. Or if you control the code to the implementation_detail class, just add the empty Copy Constructor and empty Assignment Operator.

Sjoerd
  • 6,837
  • 31
  • 44
  • We would end up with a ImplementationDetail class that has really weird copy-behavior on its own, but actually this seems like a good idea. How would you call the concept? "NoCopyOnCopy"? :-) – Martin Ba Nov 08 '10 at 12:26
  • @Martin Yes, it feels weird. And I don't know a reasonable sounding name either. But it looks like it is the most maintainable, as it pushes the weird behaviour down to the smallest context: the ImplementationDetail class (which would become my next target for refactoring - but that's beyond the scope of this answer). – Sjoerd Nov 08 '10 at 12:46
  • @Martin Note that this makes the implicit weird copy-behavior of implementation_detail more explicit and removes it from Foobar; The weirdness is already in the original code. – Sjoerd Nov 08 '10 at 12:48
0

If the Copy Constructor does not need to copy implementation_detail and still will be correct (I doubt the latter, but let's assume it for the moment), the implementation_detail is superfluous.

So the solution seems to be: make the implementation_detail static and rely on the default generated Copy Constructor and Assignment Operator.

Sjoerd
  • 6,837
  • 31
  • 44
  • `implementation_detail` could be some cache data that you'd be loathe to copy etc.. there is a huge difference between static data (iirk) and per class data and I am unwilling to assume that your change would make sense without further details. – Matthieu M. Nov 08 '10 at 09:12
  • @Matthieu But if you don't copy or reset that cache data in the Assignment Operator, it will be useless. And it is not resetted in the given Assignment Operator, so it can't be used. – Sjoerd Nov 08 '10 at 11:12
  • @Matthieu The only situation where this could be right, is when `implementation_detail` is used to reduce the number of arguments to some private implementation functions. In that case, I would favor to add it to the parameter list of those functions and turn it into a local variable in the calling member functions. And we end up with a class with a correct default Assignment Operator. – Sjoerd Nov 08 '10 at 11:14
  • 1
    I agree that we don't have enough information to know how to correctly handle it in a way that makes sense functionally :) – Matthieu M. Nov 08 '10 at 11:22