Thread: Why not use an = Operator instead of a Copy Constructor?

  1. #16
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    I see. Explain what this code snippet does:
    Code:
    two_contained(other).swap(*this);
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  2. #17
    Registered User
    Join Date
    Nov 2006
    Posts
    519
    from my std docu about swap:

    Assigns the contents of a to b and the contents of b to a.
    so I guess it's some variation of

    Code:
    void swap(T a, T b)
    {
       T temp = a;
       a = b;
       b = temp;
    }
    But I'm also guess I'm totally wrong

  3. #18
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Explain the part before the swap.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  4. #19
    Registered User
    Join Date
    Nov 2006
    Posts
    519
    Quote Originally Posted by CornedBee View Post
    Explain the part before the swap.

    Code:
      two_contained(const two_contained &other)
        : inner_list(other.inner_list), inner_vector(other.inner_vector)
      {}
    Thats the copy-ctor. It creates a new object with its members initialized to the actual values of the object passed as parameter.
    But I don't see there this copy-ctor is called from inside the assignment operator. If it's used inside the assignment operator, I guess it should look like this:

    Code:
      two_contained &operator =(const two_contained &other)
      {
        delete this;
        this( new two_contained(other) );   // looks weird, buts that that I think of if i read  
                                                               // "assignement operator in terms of copy constructor"
      }

  5. #20
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    You have a major block there
    Code:
      two_contained &operator =(const two_contained &other)
      {
        two_contained(other).swap(*this);
      }
    The bold part is the copy constructor call. It constructs a temporary object of type two_contained that is a copy of the object referenced by other.

    Then, the contents of that temporary object are swapped with those of the current object. Now the current object has the copy of other, while the temporary has the old contents of the current object. Finally, the temporary is destructed, taking with it the old value of the current object.

    By the way, I forgot a return *this at the end of the copy assignment operator.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  6. #21
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    But I don't see there this copy-ctor is called from inside the assignment operator.
    Well, take a look at the copy assignment operator:
    Code:
    two_contained &operator =(const two_contained &other)
    {
        two_contained(other).swap(*this);
    }
    It is not obvious that "two_contained(other)" invokes the copy constructor to create a temporary two_contained object? Of course, CornedBee neglected to return *this, but that is a minor omission in this context.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  7. #22
    Registered User
    Join Date
    Nov 2006
    Posts
    519
    aye, ok, I'm blind or something. sorry. probably that swap confused me a bit: why is it preferred instead of just assigning the values?

    But my main question is answered now. A temp object is created and destructed, so the original just gets new values without releasing and reacquiring any ressources. thank for your patience!
    Last edited by pheres; 10-13-2007 at 10:24 AM.

  8. #23
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    probably that swap confused me a bit: why is it preferred instead of just assigning the values?
    The idea is to ensure that the copy constructor and copy assignment operator have the same semantics. One way is to manually keep them in sync should the list of member variables change. By using a swap() member function, one now keeps the copy constructor and swap() member function in sync, and then the changes are propagated to the copy assignment operator automatically. Additionally, we now have a swap() member function that could potentially perform an optimised swap as compared to the generic swap.

    Other reasons have to do with exception safety: if the copy constructor is exception safe, and the member swap function is exception safe, then the copy assignment operator is exception safe.
    Last edited by laserlight; 10-13-2007 at 10:29 AM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  9. #24
    The larch
    Join Date
    May 2006
    Posts
    3,573
    A temp object is created and destructed, so the original just gets new values without releasing and reacquiring any ressources. thank for your patience!
    I'm not sure about that. If you create a temporary, it will aquire resources and initialize them. Swap then switches the resources. At the end, the temporary will go out of scope and release the resources in its destructor.

    All this could be optimized not to use temporary resources, but then you'd lose exception safety. With the swap technique, if creating the temporary fails (throws), the original contents will be left as they were (because we won't get to the swapping part), rather than corrupting them entirely.

    But I may be wrong.
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  10. #25
    Algorithm Dissector iMalc's Avatar
    Join Date
    Dec 2005
    Location
    New Zealand
    Posts
    6,318
    For anyone who still isn't sure about the copy-constructor call:
    Take this code snippet:
    Code:
    float f=1.23;
    int x = (int)f;
    Have you ever seen the cast written like this?:
    Code:
    float f=1.23;
    int x = int(f);
    No this isn't just a cast where someone forgot to put brackets arount the type. The reason this works (in C++) is that it is not a cast! It is a constructor call. Try it in C and see that it doesn't work. In fact if 'f' was declared as 'int' then this would be a copy-constructor call.
    Now, in this case the type is 'two_contained' not 'int', and the variable being copied is 'other' and is also of type 'two_contained'. Same syntax as above though. Look similiar now?
    Code:
    two_contained(other)
    Since the result of the expression is a new 'two_contained' object, we're free to call whatever methods on it we like, including swap. Furthur proof can be seen from the fact that 'other' is declared as a const-reference, and yet swap takes a non-const-reference, as it obviously modifies the inputs.
    Last edited by iMalc; 10-13-2007 at 01:44 PM.
    My homepage
    Advice: Take only as directed - If symptoms persist, please see your debugger

    Linus Torvalds: "But it clearly is the only right way. The fact that everybody else does it some other way only means that they are wrong"

  11. #26
    The larch
    Join Date
    May 2006
    Posts
    3,573
    I was thinking of a hybrid approach.

    For example, consider a string class that has these members: unsigned len - size of the string, unsigned capacity - size of buffer, always at least size + 1, char* buffer - dynamic character array.

    I have the following copy constructor and swap method:

    Code:
    String::String(const String& s):
        len(s.size()), capacity(s.size()+1),
        buffer(new char[capacity])
    {
        strcpy(buffer, s.c_str());
    }
    
    void String::swap(String& s)
    {
        std::swap(len, s.len);
        std::swap(capacity, s.capacity);
        std::swap(buffer, s.buffer);
    }
    Now, the copy constructor always allocates memory, but in the assignment we could simply write into the buffer without any memory allocations, if the buffer is large enough. If we assign a smaller string to a larger one, part of the buffer would just remain unused.

    Code:
    String& String::operator= (const String& s)
    {
        if (this != &s) {
            if (capacity > s.size()) { //we have enough memory
                strcpy(buffer, s.c_str());
                len = s.size();
            }
            else {
                //use temporary to create a larger memory block
                //and to delete current buffer
                String(s).swap(*this); 
            }  
        }
        return *this;      
    }
    This assignment should be quite exception safe. Allocating a larger buffer can fail (I guess this can't be avoided) but if it does, the original String will remain unmodified.
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  12. #27
    Registered User
    Join Date
    Nov 2006
    Posts
    519
    Quote Originally Posted by anon View Post
    I'm not sure about that. If you create a temporary, it will aquire resources and initialize them. Swap then switches the resources. At the end, the temporary will go out of scope and release the resources in its destructor.
    shouldn't the acquired ressources just be copied? e.g. a handle to a database connection is not created like in the constructor, rather it's just copied? I think CornedBee did point that out somethere above.

  13. #28
    The larch
    Join Date
    May 2006
    Posts
    3,573
    I meant, if the copy constructor allocates dynamic memory, then in the swap idiom: 1) memory is allocated when the temporary is constructed; 2) pointers are swapped; 3) the destructor of the temporary releases the resources that the "assigned-to" object previously held.

    If there are no memory allocations, there probably won't be much overhead.
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  14. #29
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Yes, but that's an optimization that works in a very specific case: when a class allocates memory. Oh, and when you can guarantee that your held object type has a nothrow copy constructor. E.g. std::basic_string can do it, because the character type must be a POD, but std::vector can't if it want to provide the strong exception guarantee, because any of the copy constructors might throw.

    On an irrelevant side note, memcpy would be a better choice here than strcpy.

    And pheres, I'm not sure what you mean. You can't just copy allocated resources in a copy constructor or copy assignment operator. All RAII classes must do one of three things:
    1) Create a copy of the resource.
    2) Increment a reference count on the resource.
    3) Disallow copying.

    Otherwise, you get multiple de-allocations of the resource.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  15. #30
    Registered User
    Join Date
    Nov 2006
    Posts
    519
    Hm, just to test if I got it: If one really writes the copy constructor in a way so it does acquire own ressources for the new object instead of just copying the handle to the ressources of the other object (update of reference count included), then that swap method demonstrated above in the assignement operator does not suit very well if allocating is very complex?

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Copy constructor
    By dude543 in forum C++ Programming
    Replies: 26
    Last Post: 01-26-2006, 05:35 PM
  2. illegal copy constructor required?
    By ichijoji in forum C++ Programming
    Replies: 1
    Last Post: 03-08-2005, 06:27 PM
  3. copy constructor
    By Eber Kain in forum C++ Programming
    Replies: 1
    Last Post: 09-30-2002, 05:03 PM
  4. copy constructor
    By Unregistered in forum C++ Programming
    Replies: 2
    Last Post: 09-26-2001, 05:17 PM
  5. Using strings with the copy constructor
    By Unregistered in forum C++ Programming
    Replies: 3
    Last Post: 08-29-2001, 03:04 PM