We have shallow copy and deep copy who can do the job for us when we want to copy objects in C++. So,
What is Lazy copy?
Is this a thing that is taken care by a programmer or compiler does this on its own?
What are the programming scenarios where a lazy copy is advantageous?
What is Lazy Copy?
Wikipedia Defines this aptly.
A lazy copy is a combination of both shallow copy and Deep Copy. When initially copying an object, a (fast) shallow copy is used. A counter is also used to track how many objects share the data. When the program wants to modify an object, it can determine if the data is shared (by examining the counter) and can do a deep copy if necessary.
Lazy copy looks to the outside just as a deep copy but takes advantage of the speed of a shallow copy whenever possible. The downside are rather high but constant base costs because of the counter. Also, in certain situations, circular references can also cause problems.
Is this a thing that is taken care by a programmer or something that the compiler does on its own?
The programmer has to implement this behavior for his own classes.
A compiler performs shallow copies in copying functions(copy constructor & assignment operator) by default.
Deep Copy is what a programmer has to implement for his class, so that the special handling of members(pointers) can be in place for copying functions.
What are the programming scenarios where a lazy copy is advantageous?
Ideally,
A situation wherein copying an object causes a performance penalty but the objects are not being modified very frequently the Lazy copy would be advantageous in terms of performance.
The Wikipedia cites a number of examples where, Lazy Copy(Copy On Write) is used.
Lazy copy is roughly:
perform shallow copy right away
but perform the deep copy later, only when it becomes absolutely necessary (i.e. when object is about to be modified), in hope this moment will never come.
So they are different concepts. Lazy copy is essentially a run-time optimization while shallow/deep copy is a compile-time construct that can be used to implement the lazy copy but can be used independently as well.
I'm not sure what you mean by "having shallow and deep copy who can do the job for us". Whe you write your copy constructor and assignment operator, you need to make the decision, on how you want to make a copy.
In shallow copy, you just copy the object members directly. If those members are pointers to heap memory, the copy points to the same block of heap memory. This is the default copy performed by C++ if you don't provide a copy constructor.
In a deep copy, any memory pointed to by member pointers is itself copied (potentially recursing on the members of those objects as well).
In a lazy copy, you start out with a shallow copy. If the object or its children are never modified, then you're fine. No need to make a second copy of the heap memory. When either of the two copies gets modified, a deep copy is first performed so that the modifications don't get applied to both objects. This is also known as coopy-on-write.
The purpose of lazy copying is to get the appearance of deep-copies, with some of the performance benefits of shallow copying. Whether this ends up being a performance gain depends on the usage of the objects. If you plan on having many copies of an object unmodified, then you're likely to see the advantages. If most objects are getting modified eventually anyway, then the advantages go away. When the objects get modified very frequently, the overhead of checking that the deep copy has been performed before modifying the object makes it perform worse than just a plain deep copy.
Strings are often cited as good places for lazy copying, since very often, copies are just made for displaying in different places, but most strings either use deep copies anyway, or use shallow copies, and disallow modification altogether.
Lazy Copy is used when you might generally need Deep Copy, but are not sure whether it is really necessary. Deep Copy is generally an expensive operation. If you do it unconditionally in 100% of cases and then discover that you only needed it in 10% of objects, then the efforts spent on Deep-Copying the other 90% of objects were wasted.
This is when Lazy Copy comes in. Lazy Copy is a postponed, delayed, on-demand version of Deep Copy. With Lazy Copy you don't perform Deep Copy right away. Instead, you prepare (or schedule) a Deep Copy by storing all pertinent information in the recipient object (which most of the time boils down to a mere Shallow Copy) and wait till it becomes known whether the Deep Copy is really necessary for that specific object. If it turns out to be necessary, you perform the actual Deep Copy. If the need for Deep Copy never arises, then it doesn't happen, thus saving you the effort.
Let me speak about C++
One of the very important part writing class in C++ is to implement copy constructor and overloaded = operator function.
This tutorial talks about the need of writing these functions in order to make your program more effective. Before getting into the concept lets understand basic terminology.
Constructor : Its a special function called when object of class is created. Ideally a constructor must contain the logic to initialize the data members of the class.
Copy Constructor: It is called when and object is initialized at the time of creation. There exist more scenario when an copy constructor is called.
Operator function: C++ allows to overload operator with the help of operator keyword. This helps us to treat user defined types as basic types.
Default copy constructor and =operator function which is inserted by compiler incase they are missing from the class. It performs a bit pattern copy i.e. simply copies data members of one object into another object.
Consider following code sample
class CSample
{
int x;
public:
//dafualt constructor
CSample(): x(0){}
int GetX()
{
return x;
}
};
int main()
{
CSample ob1; //Default constructor is called.
CSample ob2 = ob1; //default copy constructor called.
CSample ob3; //Default constructor called.
ob3 = ob1; //default overloaded = operator function called.
}
In the above example
CSample ob2 = ob1;
//This line will copy the bit pattern of ob1 in ob2 so the data member
// x in both the object will contain same value i.e 0.
Similarly statement
ob3 = ob1;
//This line will copy the bit pattern of ob1 in ob2 so the data member
// x in both the object will contain same value i.e 0.
The above code will work as expected until the class member is not allocated any resource (file or memory). Consider a scenario where the class is updated as follow.
class CSample
{
int *x;
int N;
public:
//dafualt constructor
CSample(): x(NULL){}
void AllocateX(int N)
{
this->N = N;
x = new int[this->N];
}
int GetX()
{
return x;
}
~CSample()
{
delete []x;
}
};
int main()
{
CSample ob1; //Default constructor is called.
ob1.AllocateX(10);
//problem with this line
CSample ob2 = ob1; //default copy constructor called.
CSample ob3; //Default constructor called.
//problem with this line
ob3 = ob1; //default overloaded = operator function called.
}
class CSample
{
int *x;
int N;
public:
//dafualt constructor
CSample(): x(NULL)
{}
//copy constructor with deep copy
CSample(const CSample &ob)
{
this->N = ob.N:
this->x = new int[this->N];
}
//=operator function with deep copy.
void operator=(const CSample &ob)
{
this->N = ob.N:
this->x = new int[this->N];
}
void AllocateX(int N)
{
this->N = N;
x = new int[this->N];
}
int GetX()
{
return x;
}
~CSample()
{
delete []x;
}
};
Related
C++11 introduced a new concept of rvalue reference. I was reading it somewhere and found following:
class Base
{
public:
Base() //Default Ctor
Base(int t) //Parameterized Ctor
Base(const Base& b) //Copy Ctor
Base(Base&& b) //Move Ctor
};
void foo(Base b) //Function 1
{}
void foo(Base& b) //Function 2
{}
int main()
{
Base b(10);
foo(b); -- Line 1 (i know of ambiquity but lets ignore for understanding purpose)
foo(Base()); -- Line 2
foo(2) ; -- Line 3
}
Now with my limited understanding, my observations are as follows :
Line 1 will simply call the copy constructor as argument is an lvalue.
Line 2 before C++11 would have called copy constructor and all those temporary copy stuff, but with move constructor defined, that would be called here.
Line 3 will again call move constructor as 2 will be implicitly converted to Base type (rvalue).
Please correct and explain if any of above observation is wrong.
Now, here'r my questions :
I know once we move an object it's data will be lost at calling location. So, i above example how can i change Line 2 to move object "b" in foo (is it using std::move(b) ?).
I have read move constructor is more efficient than copy constructor. How? I can think of only situation where we have memory on heap need not to be allocated again in case of move constructor. Does this statement hold true when we don't have any memory on heap?
Is it even more efficient than passing by reference (no, right?)?
First on your "understandings":
As I can see it, they are in principle right but you should be aware of Copy elision which could prevent the program from calling any copy/move Constructor. Depends on your compiler (-settings).
On your Questions:
Yes you have to call foo(std::move(b)) to call an Function which takes an rvalue with an lvalue. std::move will do the cast. Note: std::move itself does not move anything.
Using the move-constructor "might" be more efficient. In truth it only enables programmers to implement some more efficient Constructors. Example consider a vector which is a Class around a pointer to an array which holds the data (similar to std::vector), if you copy it you have to copy the data, if you move it you can just pass the pointer and set the old one to nullptr.
But as I read in Effective Modern C++ by Scott Meyers: Do not think your program will be faster only because you use std::move everywere.
That depends on the usage of the input. If you do not need a copy in the function it will in the most cases be more efficient to just pass the object by (const) reference. If you need a copy there are several ways of doing it for example the copy and swap idiom. But as a
Line 2 before C++11 would have called copy constructor and all those temporary copy stuff, but with move constructor defined, that would be called here.
Correct, except any decent optimizer would "elide" the copy, so that before C++11 the copy would have been avoided, and post C++11 the move would have been avoided. Same for line 3.
I know once we move an object it's data will be lost at calling location.
Depends on how the move constructor/assignment is implemented. If you don't know, this is what you must assume.
So, i above example how can i change Line 2 to move object "b" in foo (is it using std::move(b) ?).
Exactly. std::move changes the type of the expression into r-value and therefore the move constructor is invoked.
I have read move constructor is more efficient than copy constructor.
It can be, in some cases. For example the move constructor of std::vector is much faster than copy.
I can think of only situation where we have memory on heap need not to be allocated again in case of move constructor. Does this statement hold true when we don't have any memory on heap?
The statement isn't universally true, since for objects with trivial copy constructor, the move constructor isn't any more efficient. But owning dynamic memory isn't strictly a requirement for a more efficient move. More generally, move may can be efficient if the object owns any external resource, which could be dynamic memory, or it could be for example a reference counter or a file descriptor that must be released in the destructor and therefore re-aquired or re-calculated on copy - which can be avoided on move.
Is it even more efficient than passing by reference (no, right?)?
Indeed not. However, if you intend to move the object within the function where you pass it by reference, then you would have to pass a non-const reference and therefore not be able to pass temporaries.
In short: Reference is great for giving temporary access to an object that you keep, move is great for giving the ownership away.
My class A explicitly implements both its copy constructor and its copy assignment.
Which copy mechanism is used when copy assigning a vector of such elements?
Is this:
vector<A> a1, a2(5);
a1 = a2;
going to use A's copy constructor for all new elements of a1, with the elements of a2 as input?
Or is it going to make room in a1 for the elements, then use A's operator= with the elements of a2 as input?
What if a1 isn't empty before the assignment?
Is it even specified?
My class's copy constructor and operator= don't exactly do the same thing (is it bad practice? Mainly testing stuff so far). It looks like the copy constructor is called, but I wonder if it is guaranteed to be that way or if it just happens to be so in this very case.
In this context it will call the copy constructor 5 times. Since a1 is empty, there aren't any elements to assign to. So they need to be copy-constructed.
Generally, it will call whatever mixture of copy/move construction/assignment or deletion is appropriate. All depending on the sizes of the vectors in question, the particular vector operation you are performing, and the value categories of the operands.
It looks like the copy constructor is called, but I wonder if it is guaranteed to be that way or if it just happens to be so in this very case.
It could be guaranteed for the exception-safe swap-based implementation of assignment:
struct SomeClass
{
SomeClass(const SomeClass& other) { ... }
SomeClass(SomeClass&& other) { ... }
// Copy/move construction is performed while initializing the parameter
void operator=(SomeClass other)
{
this->swap(other);
}
void swap(SomeClass& other) { ... }
};
The disadvantage of such an implementation of assignment is that - because of its generality - it is not the most optimal one (for example, it does unnecessary job in case of self assignment).
In general, if exception safety concerns can be rules out, copy assigning to an object can be done faster than destroying it and constructing an in-place copy of the source object. Therefore, you should expect that performance-seeking implementations would perform assignment of objects through assignment rather than copy-construction of their sub-objects wherever possible.
I recently revisited the copy constructor, assignment operator, copy swap idom seen here:
What is the copy-and-swap idiom?
and many other places -
The Above link is an excellent post - but I still had a few more questions -
These questions are answered in a bunch of places, on stackoverflow and many other sites, but I have not seen a lot of consistency -
1 - Should you have try-catch around the areas where we allocate the new memory for a deep copy in the copy constructor ? (Ive seen it both ways)
2 - With regards to inheritance for both the copy constructor and assignment operator, when should the base class functions be called, and when should these functions be virtual?
3 - Is std::copy the best way for duplicating memory in the copy constructor? I have seen it with memcpy, and seen others say memcpy the worst thing on earth.
Consider the example Below (Thanks for all the feedback), it prompted some additional questions:
4 - Should we be checking for self assignment? If so where
5 - Off topic question, but I have seen swapped used as :
std::copy(Other.Data,Other.Data + size,Data);
should it be:
std::copy(Other.Data,Other.Data + (size-1),Data);
if swap goes from 'First to Last' and the 0th element is Other.Data?
6 - Why doesn't the commented out constructor work (I had to change size to mysize) - is assume this means regardless of the order I write them, the constructor will always call the allocation element first?
7 - Any other comments on my implementation? I know the code is useless but i'm just trying to illustrate a point.
class TBar
{
public:
//Swap Function
void swap(TBar &One, TBar &Two)
{
std::swap(One.b,Two.b);
std::swap(One.a,Two.a);
}
int a;
int *b;
TBar& operator=(TBar Other)
{
swap(Other,*this);
return (*this);
}
TBar() : a(0), b(new int) {} //We Always Allocate the int
TBar(TBar const &Other) : a(Other.a), b(new int)
{
std::copy(Other.b,Other.b,b);
*b = 22; //Just to have something
}
virtual ~TBar() { delete b;}
};
class TSuperFoo : public TBar
{
public:
int* Data;
int size;
//Swap Function for copy swap
void swap (TSuperFoo &One, TSuperFoo &Two)
{
std::swap(static_cast<TBar&>(One),static_cast<TBar&>(Two));
std::swap(One.Data,Two.Data);
std::swap(One.size,Two.size);
}
//Default Constructor
TSuperFoo(int mysize = 5) : TBar(), size(mysize), Data(new int[mysize]) {}
//TSuperFoo(int mysize = 5) : TBar(), size(mysize), Data(new int[size]) {} *1
//Copy Constructor
TSuperFoo(TSuperFoo const &Other) : TBar(Other), size(Other.size), Data(new int[Other.size]) // I need [Other.size]! not sizw
{
std::copy(Other.Data,Other.Data + size,Data); // Should this be (size-1) if std::copy is First -> Last? *2
}
//Assignment Operator
TSuperFoo& operator=(TSuperFoo Other)
{
swap(Other,(*this));
return (*this);
}
~TSuperFoo() { delete[] Data;}
};
If you allocate memory then you need to ensure that it is freed in the case of an exception being thrown. You can do this with an explicit try/catch, or you can use a smart pointer such as std::unique_ptr to hold the memory, which will then be automatically deleted when the smart pointer is destroyed by stack unwinding.
You very rarely need a virtual assignment operator. Call the base class copy constructor in the member initialization list, and base-class assignment operator first in the derived assignment operator if you are doing a memberwise assignment --- if you are doing copy/swap then you don't need to call the base class assignment in your derived assignment operator, provided that copy and swap are implemented correctly.
std::copy works with objects, and will correctly call copy constructors. If you have plain POD objects then memcpy will work just as well. I'd go for std::copy in most cases though --- it should be optimized to memcpy under the hood anyway for PODs, and it avoids the potential for errors should you add a copy constructor later.
[Updates for updated question]
With copy/swap as written there is no need to check for self-assignment, and indeed no way of doing so --- by the time you enter the assignment operator other is a copy, and you have no way of knowing what the source object was. This just means that self-assignment will still do a copy/swap.
std::copy takes a pair of iterators (first, first+size) as input. This allows for empty ranges, and is the same as every range-based algorithm in the standard library.
The commented out constructor doesn't work because the members are initialized in the order they are declared, regardless of the order in the member initializer list. Consequently, Data is always initialized first. If the initialization depends on size then it will get a duff value since size hasn't been initialized yet. If you swap the declarations of size and data then this constructor will work fine. Good compilers will warn about the order of member initialization not matching the order of declarations.
1 - Should you have try-catch around the areas where we allocate the new memory for a deep copy in the copy constructor ?
In general, you should only catch an exception if you can handle it. If you have a way of dealing with an out-of-memory condition locally, then catch it; otherwise, let it go.
You should certainly not return normally from a constructor if construction has failed - that would leave the caller with an invalid object, and no way to know that it's invalid.
2 - With regards to inheritance for both the copy constructor and assignment operator, when should the base class functions be called, and when should these functions be virtual?
A constructor can't be virtual, since virtual functions can only be dispatched by an object, and there is no object before you create it. Usually, you wouldn't make assignment operators virtual either; copyable and assignable classes are usually treated as non-polymorphic "value" types.
Usually, you'd call the base class copy constructor from the initialiser list:
Derived(Derived const & other) : Base(other), <derived members> {}
and if you're using the copy-and-swap idiom, then your assignment operator wouldn't need to worry about the base class; that would be handled by the swap:
void swap(Derived & a, Derived & b) {
using namespace std;
swap(static_cast<Base&>(a), static_cast<Base&>(b));
// and swap the derived class members too
}
Derived & Derived::operator=(Derived other) {
swap(*this, other);
return *this;
}
3 - Is std::copy the best way for duplicating memory in the copy constructor? I have seen it with memcopy, and seen others say memcopy the worst thing on earth.
It's rather unusual to be dealing with raw memory; usually your class contains objects, and often objects can't be correctly copied by simply copying their memory. You copy objects using their copy constructors or assignment operators, and std::copy will use the assignment operator to copy an array of objects (or, more generally, a sequence of objects).
If you really want, you could use memcpy to copy POD (plain old data) objects and arrays; but std::copy is less error-prone (since you don't need to provide the object size), less fragile (since it won't break if you change the objects to be non-POD) and potentially faster (since the object size and alignment are known at compile time).
If the constructor for what you're deep copying may throw something
you can handle, go ahead and catch it. I'd just let memory
allocation exceptions propagate, though.
Copy constructors (or any constructors) can't be virtual. Include a
base class initializer for these. Copy assignment operators should
delegate to the base class even if they're virtual.
memcpy() is too low-level for copying class types in C++ and can lead to undefined behavior. I think std::copy is usually a better choice.
try-catch can be used when you have to undo something. Otherwise, just let the bad_alloc propagate to the caller.
Calling the base class' copy constructor or assignment operator is the standard way of letting is handle its copying. I have never seen a use case for a virtual assignment operator, so I guess they are rare.
std::copy has the advantage that it copies class objects correctly. memcpy is rather limited on what types it can handle.
Background:
I have a complicated class with many variables. I have a sound and tested copy constructor:
Applepie::Applepie( const Applepie ©) :
m_crust(copy.m_crust),
m_filling(copy.m_filling)
{
}
Some of the member variable copy constructors called in the intializer list perform allocation.
Question:
I need to create operator=. Rather than duplicating the existing constuctor with assignment instead of initialization list, and freeing memory that's being replaced, and etc etc etc, can I simply do the following:
Applepie& Applepie::operator=( const Applepie ©)
{
if( this != ©)
{
this->~Applepie(); // release own object
new(this) Applepie(copy); // placement new copy constructor
}
return *this;
}
In other words, is destroy self followed by a placement new copy constructor semantically identical to operator= ?
This seems to have the potential to dramatically reduce repeat code and confirming that each variable is initialized properly, at the cost of potential slight loss of efficiency during assignment. Am I missing something more obscure?
Rationale:
My actual class has about 30 varaibles. I am concerned about the fact that both my copy constructor and my assignment operator have to copy all thirty, and that the code might diverge, causing the two operations to do things differently.
As Herb Sutter in "Exceptional C++" states, it is not exception safe. That means, if anything is going wrong during new or construction of the new object, the left hand operand of the assignment is in bad (undefined) state, calling for more trouble. I would strongly recommend using the copy & swap idiom.
Applepie& Applepie::operator=(Applepie copy)
{
swap(m_crust, copy.m_crust);
swap(m_filling, copy.m_filling);
return *this;
}
When your object uses the Pimpl idiom (pointer to implementation) also, the swap is done by changing only two pointers.
In addition to Rene's answer, there is also the problem of what would happen if ApplePie was a base class of the actual object: ApplePie would be replacing the object with an object of the wrong type!
I'm getting a segmentation fault which I believe is caused by the copy constructor. However, I can't find an example like this one anywhere online. I've read about shallow copy and deep copy but I'm not sure which category this copy would fall under. Anyone know?
MyObject::MyObject{
lots of things including const and structs, but no pointers
}
MyObject::MyObject( const MyObject& oCopy){
*this = oCopy;//is this deep or shallow?
}
const MyObject& MyObject::operator=(const MyObject& oRhs){
if( this != oRhs ){
members = oRhs.members;
.....//there is a lot of members
}
return *this;
}
MyObject::~MyObject(){
//there is nothing here
}
Code:
const MyObject * mpoOriginal;//this gets initialized in the constructor
int Main(){
mpoOriginal = new MyObject();
return DoSomething();
}
bool DoSomething(){
MyObject *poCopied = new MyObject(*mpoOriginal);//the copy
//lots of stuff going on
delete poCopied;//this causes the crash - can't step into using GDB
return true;
}
EDIT: Added operator= and constructor
SOLVED: Barking up the wrong tree, it ended up being a function calling delete twice on the same object
It is generally a bad idea to use the assignment operator like this in the copy constructor. This will default-construct all the members and then assign over them. It is much better to either just rely on the implicitly-generated copy constructor, or use the member initializer list to copy those members that need copying, and apply the appropriate initialization to the others.
Without details of the class members, it is hard to judge what is causing your segfault.
According to your code you're not creating the original object... you're just creating a pointer like this:
const MyObject * mpoOriginal;
So the copy is using bad data into the created new object...
Wow....
MyObject::MyObject( const MyObject& oCopy)
{
*this = oCopy;//is this deep or shallow?
}
It is neither. It is a call to the assignment operator.
Since you have not finished the construction of the object this is probably ill-advised (though perfectly valid). It is more traditional to define the assignment operator in terms of the copy constructor though (see copy and swap idium).
const MyObject& MyObject::operator=(const MyObject& oRhs)
{
if( this != oRhs ){
members = oRhs.members;
.....//there is a lot of members
}
return *this;
}
Basically fine, though normally the result of assignment is not cont.
But if you do it this way you need to divide up your processing a bit to make it exception safe. It should look more like this:
const MyObject& MyObject::operator=(const MyObject& oRhs)
{
if( this == oRhs )
{
return *this;
}
// Stage 1:
// Copy all members of oRhs that can throw during copy into temporaries.
// That way if they do throw you have not destroyed this obbject.
// Stage 2:
// Copy anything that can **not** throw from oRhs into this object
// Use swap on the temporaries to copy them into the object in an exception sage mannor.
// Stage 3:
// Free any resources.
return *this;
}
Of course there is a simpler way of doing this using copy and swap idum:
MyObject& MyObject::operator=(MyObject oRhs) // use pass by value to get copy
{
this.swap(oRhs);
return *this;
}
void MyObject::swap(MyObject& oRhs) throws()
{
// Call swap on each member.
return *this;
}
If there is nothing to do in the destructor don't declare it (unless it needs to be virtual).
MyObject::~MyObject(){
//there is nothing here
}
Here you are declaring a pointer (not an object) so the constructor is not called (as pointers don;t have constructors).
const MyObject * mpoOriginal;//this gets initialized in the constructor
Here you are calling new to create the object.
Are you sure you want to do this? A dynamically allocated object must be destroyed; ostensibly via delete, but more usually in C++ you wrap pointers inside a smart pointer to make sure the owner correctly and automatically destroys the object.
int main()
{ //^^^^ Note main() has a lower case m
mpoOriginal = new MyObject();
return DoSomething();
}
But since you probably don't want a dynamic object. What you want is automatic object that is destroyed when it goes out of scope. Also you probably should not be using a global variable (pass it as a parameter otherwise your code is working using the side affects that are associated with global state).
int main()
{
const MyObject mpoOriginal;
return DoSomething(mpoOriginal);
}
You do not need to call new to make a copy just create an object (passing the object you want to copy).
bool DoSomething(MyObject const& data)
{
MyObject poCopied (data); //the copy
//lots of stuff going on
// No need to delete.
// delete poCopied;//this causes the crash - can't step into using GDB
// When it goes out of scope it is auto destroyed (as it is automatic).
return true;
}
What you are doing is making your copy constructor use the assignment operator (which you don't seem to have defined). Frankly I'm surprised it compiles, but because you haven't shown all your code maybe it does.
Write you copy constructor in the normal way, and then see if you still get the same problem. If it's true what you say about 'lots of things ... but I don't see any pointers' then you should not be writing a copy constructor at all. Try just deleting it.
I don't have a direct answer as for what exactly causes the segfault, but conventional wisdom here is to follow the rule of three, i.e. when you find yourself needing any of copy constructor, assignment operator, or a destructor, you better implement all three of them (c++0x adds move semantics, which makes it "rule of four"?).
Then, it's usually the other way around - the copy assignment operator is implemented in terms of copy constructor - copy and swap idiom.
MyObject::MyObject{
lots of things including const and structs, but no pointers
}
The difference between a shallow copy and a deep copy is only meaningful if there is a pointer to dynamic memory. If any of those member structs isn't doing a deep copy of it's pointer, then you'll have to work around that (how depends on the struct). However, if all members either don't contain pointers, or correctly do deep copies of their pointers, then the copy constructor/assignment is not the source of your problems.
It's either, depending on what your operator= does. That's where the magic happens; the copy constructor is merely invoking it.
If you didn't define an operator= yourself, then the compiler synthesised one for you, and it is performing a shallow copy.