views:

608

answers:

7

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.

+1  A: 

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;
}
AraK
I could... but it would be better if I didn't even have to try to delete it when I *know* it hasn't been initialized.
Mark
There is no harm deleting NULL pointers. Anyway, the assignment operator should free original resource and allocate new resources to copy the other object's data.
AraK
I know it's safe, but it's still an extra operation ;) One that, IMO, shouldn't be needed. I know it's a micro-optimization, but if it can clearly be avoided, I'd prefer that.
Mark
Btw I want to give you a +1 but it won't let me. Says your post is too old. Try editing it slightly.
Mark
There is no problem, I enjoyed thinking about your question :)
AraK
sellibitze
+7  A: 

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

Alex Martelli
Assuming I did need to free some resources, then this method wouldn't work at all eh? Because then I'd by trying to free uninitialized resources in the copy ctor (since it calls operator=). Which leads to the last question I had -- is there anyway to swap it so that `operator=` calls the copy constructor instead? That way I free the resources and then copy the data.
Mark
Best is probably to have "free stuff" and "copy stuff" auxiliary methods and call them to build up copy ctor, assignment, and dtor -- not quite as general like litb's std::swap idea, but somewhat cheaper if assignment and copy-constructing are equivalent for the resources you're holding.
Alex Martelli
+1: After reading litb's answer and the other approaches I decide to like your's the most. Yes, it does not look very elegant but it combines no code redundancy with best performance. (See also my comment to litb's and Goz' answers)
rstevens
I've got this sneaking suspicion that if assignment and copy-construction are equivalent (i.e, there are no resources to free), then the default copy constructor and operator= are fine, and you shouldn't be writing any code at all. But I can't prove it, and anyway the default operator= and this approach both have exception-safety issues which only copy-and-nothrow-swap can solve, unless you know that all your members have nothrow operator=. If you use exceptions, then copy-and-swap ranks roughly with RAII as an essential c++ idiom.
Steve Jessop
... the problem with implementing operator= as "free, then copy" if the copy can fail, is that you can provide at best the weak exception guarantee, and even for that you have to catch any exceptions and undo what you've done of the "copy", so as to clear out the object being assigned to, into an "empty" state. This is neither its initial state, nor the desired state, hence no strong guarantee.
Steve Jessop
+3  A: 

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.

gatorfax
+17  A: 

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).

Johannes Schaub - litb
That sounds great, but why do I need to swap them? Who cares what the value of f is? Isn't that needless copying? Not to mention I copied f just to call the function since you removed the reference...
Mark
Before you swap, the values you want are in `f`, but you want them in `*this`. That's why you swap. If you keep `f` being a const reference, then you have to do `Fixture c = f; swap(*this, c);` in the body - *so you have to copy anyway*. It's always best to do a copy as transparent to the caller as possible. Read http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/
Johannes Schaub - litb
Yes, I want the values from `f` in `*this`. But I don't need `*this` copied to `f`. Sure there's an assignment, but this causes twice the assignments, no?
Mark
swapping is the only way you can get over the values from `f` to `*this` using the copy constructor, i think. In this simple case where you just need to copy over a couple of fields, you seems to be also able to use the default assignment operator. For advanced cases where you need to provide your own, you usually have the need to deeply copy your object. In this case, a swap function can conveniently just exchange handles from `*this` and `f`. That the values of `*this` end up into `f` is just a side-effect which doesn't hurt. It will get destructed when the operator function returns.
Johannes Schaub - litb
It's common practice to add a `swap` function having two `Fixture` instead (in addition to having the namespace scope function, which is called when other code does a swap).
Johannes Schaub - litb
Oh wait, dammit `std::swap` will use our assignment operator in the end... well i guess then we will have to write the member assignments out :)
Johannes Schaub - litb
Fixed - i apologize for the first answer - didn't think when i wrote that `swap` line! :)
Johannes Schaub - litb
This is just getting more and more ugly...
Mark
I think it's getting more and more elegant :O
GMan
I don't think this idiom applies to shallow copies: you are just moving the reduplicated listing of all members from = to swap. With shallow assignments, a) don't implement it, 2) wait for C++0x and = default;
UncleBens
@UncleBens, agreed. That's why i removed my initial swap. I somehow thought of swap as magically swapping the two objects, not considering that it will indeed call back to `operator=` again thus recursing infinitely. For shallow copies like in his code example, there is no need for a custom `operator=` in the first place.
Johannes Schaub - litb
@UncleBens: You're right: We still have a member-to-member do something code twice in our class (1st in the copy constructor, 2nd in the swap method). So I also don't really like this approach. Also performance is worse than a "normal" operator=() because now you have to copy and to swap. Otherwise operator=() just copies.So no solution to the problem of double code.
rstevens
@rstevens, well my answer is about deep copying - you will implement swap and assignment the same time, while assignment makes use of the copy constructor. Swapping is a very cheap operation for those kind of classes. I do not try to show a way to implement an assignment operator copying N primitive variables over (my initial application of copy/swap to it was a mistake). I think that's where the implicitly defined assignment operator should be used, or where you really should use another function like @Alex recommends if you still need to change anything (logging maybe).
Johannes Schaub - litb
@litb: You're right that swapping is cheap for most classes but since you still copy it (sure you must since that's the meaning of the operator=() ) and swap it afterwards you are still a little bit slower what would be absolutely OK if you safe typing nearly the same code in the cctor and the operator=(). So as soon as swap() keeps much shorter (in code) as the cctor your approach is a good one :-)
rstevens
At least, my performance tests show that it's faster than both the `this->~Fixture();` stuff and than manually copying the code of the copy constructor (test code here: http://codepad.org/ZoYa44N2 , with `g` in a separate TU with empty body and compiled with `-O2`) - although only very slightly so. The Copy/Paste gets 0m4.873s , my one gets 0m4.177s and the destruct-then-create-again gets 0m4.953s .
Johannes Schaub - litb
to be fair it's only a test though. I encourage you to do tests too and at the end we could combine our results.
Johannes Schaub - litb
However, if the copy constructor/assignment of any of the members could throw, wouldn't copy-and-swap still increase exception safety - assuming nothrow swap for all members? The idea is that you have made sure the copy could be created, before you actually start to mess with *this. And if anything goes wrong, at least *this is left unmodified.
UncleBens
@UncleBens: That's true. It's the big advantage of this approach!
rstevens
Why don't I just swap the pointers in the assignment operator, and assign the primitive data types like normal then? Swapping pointers makes sense I guess, but swapping primitive data types doesn't really do much good does it?
Mark
What would be the difference between the pointer and an integer with regard to this? Either you swap both, or you assign both, i think. The point of swapping, though, is that it can be done very fast, and without risking an exception being thrown, while assignment *copies*.
Johannes Schaub - litb
There are two cases here: 1) You have primitive data. Then the swap is superfluous and you can instead just use assignment. 2) You have heavy user defined data. The swap isn't superfluous and can enable much performance boost. => In the end, in 1) the compiler can optimize out any dead write - since it's primitive data it's easy for it. And even if not - we talk about a single additional assignment to some 4 byte object. And in 2), the swap is indeed good.
Johannes Schaub - litb
Now, i just do the swap because it's convenient and doesn't cost any performance - it rather increases readability, because we have a row of swaps and we immediately see the pattern in the code.
Johannes Schaub - litb
Wait - we are really overlooking something here: You *need* to swap, because you want your old date to be freed. If you just assign over, you don't free the old data. And if you just swap the pointer, but not the other data, then in the destructor of the parameter (which will hold your old pointer, but some other data), you will get inconsistencies. So you have to always swap.
Johannes Schaub - litb
+3  A: 

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.

Adrian
Well that's great and all, but it doesn't really answer anything does it? All you've told me is that I *should* include them. Not *how*, which was what the question was about.
Mark
+1 for useful tidbit of info, although Mark is correct that this does not answer his question at all
DVK
I guess my point was that better minds than mine have thought about this problem and in Coplien's case written an entire chapter section on it. So, rather than completely plagiarizing Coplien, I included a reference to a book that is worth reading. I will edit to offer a bit more of an opinion/answer.
Adrian
A: 

If you re-write your

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

as

Fixture& Fixture::operator=(const Fixture &f) {
    Fixture* p = new( this ) Fixture( f );
    return *p;
}

This then uses "placement new" to construct the this pointer.

Goz
And that doesn't create a memory leak? Do we even need `void *p` or can we leave that part out?
Mark
No it doesn't memory leak .. no memory is being allocated. All it does is run the constructor on the block of memory passed in. I have actually edited my code to return the p because otherwise I have seen this optimised out by compilers (this was 10 years ago though) so you'd probably be fine just ignoring the void* p and returning *this.
Goz
Awesome. I'm going to mark this as the answer then. Nice, clean, and efficient :)
Mark
Isn't the result of that that you have an object whose constructor has been run multiple times (calls to constructor don't match up calls to destructor -> could leak, if any of the members had a non-trivial destructor).
UncleBens
rama-jka toti
"gotw.ca/gotw/023.htm" warns against this idiom. Even with the destructor that you forgot.
stefaanv
I don't think this is a good solution. How about for example de-allocating memory that will be reallocated in the constructor? (If you don't care you got memory leaks). Don't forget that this also calls the base constructors. So if you have inheritance and also want to call the base assignment operator you will call the base constructor twice (if you use this "trick" in your base assignment operator too).
rstevens
-1, the code doesn't compile (dereferencing `void *`), is incorrect (fails to call the desctructor) and most importantly it is non-idiomatic (I'm not sure if it involves undefined behavior, but I wouldn't be surprised).
avakar
Eh? How is being non-idiomatic *more important* than being incorrect? You prefer broken, idiomatic code to working, idiosyncratic code?
Steve Jessop
Heh ... I never said it was a good plan ... the only time i've actually used placement new was for intialising DMA chain tags in a long block of memory allocated for the DMA chains. This was far faster than using operator = for constructing the relevant data in place and still allowed us to use my easy-to-read constructor based DMA Tag classes. Still always useful to know what tools you have about.
Goz
@Goz: "placement new" was ment to be used as you now describe it: mapping a class at some address, not to create a prettier assignment operator.
stefaanv
onebyone, fixing incorrect code is generally easy, after all the missing destructor call is a one-liner. On the other hand, maintaining badly written code can become a nightmare. Yes, I do prefer idiomatic code to a correct one.
avakar
+5  A: 

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)

sellibitze
good point. that's what i think about the example code too. Not sure why it needs its own copy assignment operator.
Johannes Schaub - litb
Don't mean to be rude, but read the first sentence :) I'm inheriting from another class that implicitly disables the copy constructor and assignment operator. I need to define them in my own class for them to exist.
Mark
You could have mentioned this detail. I think it's fairly unusual to derive from a noncopiable class and make the derived one copiable.
sellibitze