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

  1. #31
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Why not? Actually, it's perfectly suited. Or else I really don't get what you mean.
    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

  2. #32
    Registered User
    Join Date
    Nov 2006
    Posts
    519
    I have object 1, to which I want to assign the actual values of object 2.

    Method 1 (the one demonstrated here):
    So I'm creating a temp object inside the assignment operator as a copy of object 2 (*), to reuse the copy constructor.
    After that I call swap to actually assign the new values to object 1.

    Method 2:
    Just assign the value of every variable of object 2 to object 1 without using the copy constructor.

    (*) If that copy construction takes very long - maybe think of mounting a remote directory on another continent over a 9600 baud connection to get an object representing that directory - isn't then method 2 not far far better?

    somethere above must be my error...

  3. #33
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    If that copy construction takes very long - maybe think of mounting a remote directory on another continent over a 9600 baud connection to get an object representing that directory - isn't then method 2 not far far better?
    Yes (in terms of efficiency), but then copy construction and copy assignment no longer follows the same semantics: copy construction causes the new object to acquire its own resources, copy assignment causes the object to have handles to the other object's resources. This difference in behaviour is likely to be undesirable.

    To avoid this, the copy assignment operator would have to use the network as well... upon which one can go back to the copy constructor+swap idiom and not be any worse off.
    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

  4. #34
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    I pointed out the 3 valid methods of copy behaviour. They apply to copy assignment just as much as copy construction.

    Take the mount example. Object A is constructed and mounts the remote share. Its destructor will eventually unmount the share.
    Object B is made a copy of object A. It doesn't matter whether it's through copy assignment or copy construction. Now object B is naive and just copies over A's variables.
    Object B is destructed. It unmounts the share.
    Object A is asked to perform some operation that needs the share. But B unmounted it. Object A makes the program crash.

    Oops.

    Object B can do two things: it can either increment a reference count on the mount, and only unmount if the count drops to zero, or it can mount the remote share again in its own private location (thus copying the resource). There are no other options. And it doesn't matter whether this was done through copy assignment or copy construction.

    Disallowing copying is another option (and perhaps a sensible one, if copying is so slow), but then you wouldn't have an object B.

    But the point is, it all applies equally to copy assignment and copy construction. And swap() is still O(1), fast and exception-safe.

    The only time you should implement copy assignment differently is when you can reuse the old resource and still maintain the same guarantees. basic_string can reuse its block of memory. vector can do it if it detects a POD member type. If your remote share is always the same, you could avoid mounting and unmounting. (But then, because it's so slow, you should have one single global mount shared by all the objects.)
    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

  5. #35
    Algorithm Dissector iMalc's Avatar
    Join Date
    Dec 2005
    Location
    New Zealand
    Posts
    6,318
    Pheres: Apart from the problems others have pointed out with Method 2, don't forget that you have to deal with disposing of the current resources allocated by the object you're assigning to.
    The swap method takes care of this because the existing values are of the object being assigned to are swapped to the temporary object which then immediately goes out of scope and gets destroyed. It's all rather clever actually.
    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"

  6. #36
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    I'm impressed at the heights of complexity this discussion has reached. Obviously, writing copy constructors and assignment operators which work as similarly as possible (within the bounds of sense), are efficient, exception safe, and easy to understand and maintain is HARD.

    One of the principles I adhere to in writing C++ code is to avoid creating copy constructors and assignment operators at almost all costs, because of this exact problem. It is almost always possible to create classes that do what you want by wrapping types in container types which enforce specific types of copy behaviors.

    As a bonus, designs based on containers are exception-safe automatically.

  7. #37
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    That's the key. Make every resource that needs handling be wrapped by its own class. Thus, the complexity of the big three never rises beyond that single resource, and classes using the resource needn't bother with anything.
    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

  8. #38
    Registered User
    Join Date
    Nov 2006
    Posts
    519
    Thank you guys, that discussion made my mind a lot clearer about this topic.

    Two questions: The term "big three" does refer to acquire own ressource / increment ref.count / disallow copying? Or is it something else?

    It is almost always possible to create classes that do what you want by wrapping types in container types which enforce specific types of copy behaviors.
    How would that apply to the mount example? Or could you give another short example demonstrating that? Thank you

  9. #39
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    The term "big three" does refer to acquire own ressource / increment ref.count / disallow copying?
    I believe it refers to the copy constructor, copy assignment operator and destructor.
    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

  10. #40
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by pheres View Post
    How would that apply to the mount example? Or could you give another short example demonstrating that? Thank you
    In the mount example I would actually make the class non-copyable. Object B would then be a "view" of object A rather than a copy of A itself -- in other words, a different class entirely.

    But first I would question the need for a copy or a view of A in the first place.

  11. #41
    Registered User
    Join Date
    Aug 2005
    Posts
    204
    Thanks everyone for all the discussion. Can anyone explain why my simple class for a point won't compile?

    Code:
    class point{
      public:
         point(){;}
         ~point(){;}
         point(const point&);
         double v[2];
         void setpoint(double, double, double);
         point &operator =(const point &rhs);
         double &operator[] (unsigned i) { return v[i]; }        
    };
    
    point &point::operator =(const point &rhs){      
          v[0] = rhs[0];
          v[1] = rhs[1];
          v[1] = rhs[2];      
          return *this;      
    }
    
    point::point (const point &pt) {
          *this = pt;    
    }
    
    void point::setpoint(double a, double b, double c) {
          v[0] = a;
          v[1] = b;
          v[2] = c;
    }

  12. #42
    The larch
    Join Date
    May 2006
    Posts
    3,573
    Code:
    point &point::operator =(const point &rhs){      
          v[0] = rhs.v[0];
          v[1] = rhs.v[1];
          v[2] = rhs.v[2];  //out of bounds    
          return *this;      
    }
    However, in this case it seems to me that you don't need to write the copy contructor and assignment at all, as they don't do anything other than a shallow copy (memberwise copy). (This might also be why you might be able to implement the copy constructor in terms of assignment and not the other way round, as the thread discusses.)
    Last edited by anon; 10-15-2007 at 11:19 AM.
    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).

  13. #43
    Algorithm Dissector iMalc's Avatar
    Join Date
    Dec 2005
    Location
    New Zealand
    Posts
    6,318
    double v[2];
    needs to be
    double v[3];

    Post your compile error messages.
    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"

  14. #44
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by pheres View Post
    Or could you give another short example demonstrating that? Thank you
    Imagine you have a class A which holds some member by pointer:

    Code:
    class A
    {
    private:
        Member *b;
    };
    Conceptually, class A "owns" the object b. For unspecified reasons, it must be referenced via a pointer and not held directly by value. The copy semantics are such that if an object of class A is copied, the "b" object is DUPLICATED and the result holds a pointer to a NEW object. You could implement this by writing a custom assignment operator:

    Code:
    A &operator=(const A &other)
    {
        delete b; // Free the one I have
        b = new Member(*(other.b)); // Copy the other one
    }
    Or b could be copied by first default-constructing it and then using Member's assignment operator. But a much better solution is to create a wrapper type which handles the copying of the pointed-to object for you:

    Code:
    template <typename T>
    class copied_ptr
    {
    public:
        copied_ptr(T *ptr);
            : obj(ptr)
        {
        }
    
        copied_ptr(const copied_ptr &other)
            : obj(other.obj ? new T(*(other.obj)) : 0)
        {
        }
    
        copied_ptr &operator=(const copied_ptr &other)
        {
            delete obj;
            obj = other.obj ? new T(*(other.obj)) : 0;
            return *this;
        }
    
        // This type can automatically convert to the underlying pointer type.
        operator T *()
        {
            return obj;
        }
    
        // An explicit get-pointer method for when the above automatic conversion is not appropriate.
        T *get()
        {
            return obj;
        }
    
        // It also allows member dereference
        T *operator->()
        {
            return obj;
        }
    
    private:
        T *obj;
    };
    Then, the definition of class A would look like:

    Code:
    class A
    {
    private:
        copied_ptr<Member> b;
    };
    Class A no longer needs a custom assignment operator. The default assignment operator is sufficient, because it invokes the assignment operator of copied_ptr<Member> which does the right thing.

    You might be thinking "Holy cow, that's a lot of code, compared to the two lines in the custom assignment operator..." The difference is, you only have to write copied_ptr<> ONCE. Once you have it, you can use it anywhere you need THOSE specific copy semantics. And once you get it right, it is going to STAY right. Compare that with writing zillions of assignment operators everywhere you need this concept. You are bound to make a mistake in one of them.

    Hopefully that illustrates the basic idea behind wrapping types in order to cause certain copy behaviors. PLEASE don't use the copied_ptr<> template. I whipped it up just for this example, so it is missing features and is nowhere near as useful as the real copied_ptr<> I use. Also, it might have errors in it, although I don't see any obvious ones. Point being, it is just an illustration.

  15. #45
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    >> when some type is not default constructable - shame on you!
    Sorry, this was from way earlier in the thread, but since I'm posting anyway I'll say that I completely disagree. Only provide default construction when it makes sense for the class. If you provide it when not necessary then "shame on you".



    BTW, brewbuck, does the copied_ptr<> you use require the class to implement a clone() function or something similar? How does it do with base and derived classes?

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