Exception Safe Assignment Operator Syntax

Copy Constructors and Copy Assignment Operators (C++)

Note

Starting in C++11, two kinds of assignment are supported in the language: copy assignment and move assignment. In this article "assignment" means copy assignment unless explicitly stated otherwise. For information about move assignment, see Move Constructors and Move Assignment Operators (C++).

Both the assignment operation and the initialization operation cause objects to be copied.

  • Assignment: When one object's value is assigned to another object, the first object is copied to the second object. Therefore,

    causes the value of to be copied to .

  • Initialization: Initialization occurs when a new object is declared, when arguments are passed to functions by value, or when values are returned from functions by value.

    You can define the semantics of "copy" for objects of class type. For example, consider this code:

The preceding code could mean "copy the contents of FILE1.DAT to FILE2.DAT" or it could mean "ignore FILE2.DAT and make a second handle to FILE1.DAT." You must attach appropriate copying semantics to each class, as follows.

  • By using the assignment operator together with a reference to the class type as the return type and the parameter that is passed by reference—for example .

  • By using the copy constructor.

    If you do not declare a copy constructor, the compiler generates a member-wise copy constructor for you. If you do not declare a copy assignment operator, the compiler generates a member-wise copy assignment operator for you. Declaring a copy constructor does not suppress the compiler-generated copy assignment operator, nor vice versa. If you implement either one, we recommend that you also implement the other one so that the meaning of the code is clear.

    The copy constructor takes an argument of type class-name&, where class-name is the name of the class for which the constructor is defined. For example:

Note

Make the type of the copy constructor's argument const class-name& whenever possible. This prevents the copy constructor from accidentally changing the object from which it is copying. It also enables copying from const objects.

Compiler generated copy constructors

Compiler-generated copy constructors, like user-defined copy constructors, have a single argument of type "reference to class-name." An exception is when all base classes and member classes have copy constructors declared as taking a single argument of type constclass-name&. In such a case, the compiler-generated copy constructor's argument is also const.

When the argument type to the copy constructor is not const, initialization by copying a const object generates an error. The reverse is not true: If the argument is const, you can initialize by copying an object that is not const.

Compiler-generated assignment operators follow the same pattern with regard to const. They take a single argument of type class-name& unless the assignment operators in all base and member classes take arguments of type constclass-name&. In this case, the class's generated assignment operator takes a const argument.

Note

When virtual base classes are initialized by copy constructors, compiler-generated or user-defined, they are initialized only once: at the point when they are constructed.

The implications are similar to those of the copy constructor. When the argument type is not const, assignment from a const object generates an error. The reverse is not true: If a const value is assigned to a value that is not const, the assignment succeeds.

For more information about overloaded assignment operators, see Assignment.

So the user will be responsible to create new, while the smart pointer class helps to clean it up?

Yes, typically this is what is done. Example:

One of the reasons the construction (and allocation) is left to the client is because T could have all kinds of parameterized constructors. It would be tricky, to say the least, and probably involve variadic constructor templates with some really fancy template magic if you wanted to allow a smart pointer to construct T on its own with all possibilities. It's simpler just to let the client pass in the pointer on constructing the smart pointer. As long as they do that right away on construction, it's safe, since if throws before we get to the smart pointer construction, nothing will be allocated/constructed, and so there will be nothing to clean up except what's already on the call stack (which should conform to RAII for exception safety).

If you want to make it robust for API boundaries, then you usually want to capture a "deleter" which will call 'operator delete' at the same site in which the code was generated (important when working across module boundaries where trying to free memory in module B for memory allocated in module A would yield undefined behavior).

However, copy-n-swap requires the object is passed in by value (not by reference). Can it still be used in designing a smart pointer? My concern was that this is a pointer class, hence may not be able to pass in by value. But I'm not very sure about this.

For this kind of smart pointer you are making which implements no reference counting, typically the best design is to disallow copying (both assignment and copy ctor, though move ctors are fine).

Otherwise you're going back to the archaic kind of practices of transferring ownership (as is the case with the ancient ) or trying to deep copy the pointee.

Transferring ownership can be especially prone to human error, since it treats the source it's copying from as mutable (which is completely unusual and trippy behavior). If you do that though, you can use atomic CAS to swap pointers, but you need to make the copy constructor and assignment operator accept things by reference, not const reference or by value, since it's going to treat the source as mutable (either that or make the private member you have mutable and use const reference).

Deep copying the pointee is a kind of interesting idea, but one problem with that is that someone may try to copy your smart pointer at a site where T is not a complete type (not defined, only declared). It's similar to the problem with destructors, so if you want to make a deep copying smart pointer like this, a robust solution would be to capture the copy constructor of T (ex: store a function pointer to a function you generate when your smart pointer is constructed to clone/copy construct new elements of T).

Also slight fix for your existing deep-copying code:

3: This must be a dumb question, but better ask then keep doubts inside. for SmartPointer& sp to access ptr, should it use sp->ptr or sp.ptr??

Let the compiler fix your doubts. Given a reference, , would be a compiler error in that case unless has a member called you can access, which is probably not what you want anyway. would call your overloaded , not access the actual private member of the smart pointer. is only defined normally for pointers, so trying to use it on a reference or const reference would invoke your overloaded, user-defined operators.

0 comments

Leave a Reply

Your email address will not be published. Required fields are marked *