How to deal with copying without copy constructors?

I have a custom dynamic array class and one of it's members is of course a pointer to memory which holds the current array. Recently, I had a bug which was the result of the assignment operator performing a shallow copy on a struct with a dynamic array and me modifying that array copy not realizing this copy still had the same pointer to the original memory block. In the C++ world this is where you're suppose to create a custom copy constructor for the dynamic array class so that I can perform a deep copy of any relevant pointer members to avoid the above issue. My hesitation with this is I know Casey doesn't advocate using any kind of c++ constructors for various reasons and I tend to agree with him. In this situation however, what is usually the best practice to resolve the shallow copy vs deep copy issue without the use of a copy/assignment operator constructor?

I guess I could just write a function to perform the copy but then that would require me to have to know which classes contain dynamic vectors to prevent me from casually using the assignment operator (=) by accident. This seems like it would become cumbersome, especially in situations where my dynamic array is hidden a few structs deep:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
struct Thing0
{
   DynamArray arr{};
} 

struct Thing1
{
   Thing0 thing0;
}

struct Thing2
{
   Thing1 thing1;
}

Thing2 thing;
Thing2 otherThing = thing; //oops! shallow copy performed!

//Needed to do this instead
Thing2 otherThing;
CopyThing(&otherThing, thing);


Also, what about functions? With no copy constructor function parameters would automatically perform a shallow copy which could cause more bugs for me in the future.

Edited by Jason on Reason: Initial post
You can declare the copy construct/assign as deleted. That way an errant shallow copy becomes a compile error.

However the C++ RAII semantics aren't all that bad. Sure they aren't perfect (syntax especially), however with the move to move-focused semantics in recent years excessive hidden deep copies can be avoided.

Okay, so I shouldn't be too worried about having to write these types of things. But how does Casey handle these types of situations in his code base since he doesn't use them? Does he code in such a way to avoid this type of situation? I'm only on vid 120 something and so far he hasn't really mentioned this type of situation to my knowledge.

Edited by Jason on
AFAICT He doesn't use dynamic arrays. Which means that he just doesn't run into that kind of issue.

His arena allocation style kind of precludes a lot of solutions that use ad-hoc allocations.
My two cents: I think that most of the time, I want a POD (Plain Old Data) copy and when I need to get a "deep copy" I want to be explicit about it (e.g. I need to have a reason for allocating memory and copy the data). So the problem of modifying the wrong data never really occurs or is easy enough to spot. I only use C and the "default is POD copy, be explicit for deep copy" way seem to just flow and fit together well.
mrmixer
My two cents: I think that most of the time, I want a POD (Plain Old Data) copy and when I need to get a "deep copy" I want to be explicit about it (e.g. I need to have a reason for allocating memory and copy the data). So the problem of modifying the wrong data never really occurs or is easy enough to spot. I only use C and the "default is POD copy, be explicit for deep copy" way seem to just flow and fit together well.


So if you had a situation like I described above you would handle it something like this?:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct DynamArray
{
   DynamArray()
   {
      elements = Allocate(4000);
   }
   //some other stuff
   void* elements;
}

struct Thing0
{
   DynamArray arr{};
} 

struct Thing1
{
   Thing0 thing0;
}

struct Thing2
{
   Thing1 thing1;
}

Thing2 originalThing;
Thing2 newThing;

CopyArr(&newThing.thing0.arr, originalThing.thing0.arr);


If so, do you find it annoying to do that instead of just being able to use a '=' operator? This way also means that you have to be cognizant of everything that is in Thing2 cause if you forget to call copy then you might just use '=' by accident and have a bug.

The solution there is to avoid copying your structs all over the place.

It's going to be a mentality shift in your code style where you pass everything by pointer unless you actually want a copy (deep or shallow).
I my codebase I never have more than a struct containing a struct (1 level). I don't have a rule for that, it's what emerged of my coding style. That makes it easy to know what's inside a given struct (only look at 1 struct definition).

As I use only C, I don't use operator overloading, function overloading, copy constructor… and it's not a problem for me. I prefer that there is no ambiguity in the code: if the "=" operator can mean "copy" or "deep copy" and you have no way of knowing without looking at the source, than I don't thing it's a good idea. Just the fact that it's possible to overload an operator make all C++ code ambiguous (for me).

If the '=' operator does a deep copy, you may need to write a function to do a shallow copy (if you need that at some point). So you would need to write two functions to have the same result as writing one.

I don't think that having to know what's inside a struct is a problem either. If you want to operate on some data, you know the data is in the struct. If you don't want to operate on the data, then you don't want it to be allocated and copied for no reason.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
typedef struct dyn_array_t {} dyn_array_t;

void dyn_array_initialize( dyn_array_t* array,  ) {}
void dyn_array_copy( dyn_array_t* from, dyn_array_t* to ) {}

typedef struct thing_t {
   dyn_array_t array;
   
} thing_t;

void thing_initialize( thing_t* thing ) {
    dyn_array_initialize( &thing->array,  );
    
}

Thing a, b;
thing_initialize( &a );

b = a;
dyn_array_copy( &a->array, &b->array );

Edited by Simon Anciaux on Reason: code fix
Ah, those last couple responses really helped, thanks guys. I think I'm just trying to find different ways to look at these kinds of problems since my mind is still polluted with c++/oop ideas. Hard to break away from sometimes