Jump to content

  • Log In with Google      Sign In   
  • Create Account

#ActualHodgman

Posted 05 February 2013 - 09:03 PM

However, I (think) I still don't understand the uses of the copy ctor/assignment op;

class Foo{
private:
    int data
public:
/*normal ctor*/
    Foo(int d) : data(d) {};
/*copy ctor*/
    Foo(const &Foo copy) : data(copy.data){};
/*assignment op*/
    Foo& operator=(const &Foo copy){ data = copy.data };
};
is the above correct?

Well, it behaves as you'd expect... except the assignment operator should be:
Foo& operator=(const &Foo copy){ data = copy.data; return *this; };
It breaks the rule of 3 though -- either you should have 0 of the 3 things in the rule, or 3 of the 3 things. You've only written 2 of them. The rule is: if you have a copy-ctor, assignment-op or destructor, you should have all three of them.

However, the copy-ctor and assignment-op aren't needed for such a simple class -- the compiler will generate them if you don't write them yourself, so that class should just be:

class Foo{
    int data
public:
    Foo(int d) : data(d) {};
};

This also satifies the rule, because you have 0 out of 3 of those things now.

if so, what do they have to do with dynamically allocated memory?

In your example, the only member is an int, so no custom copy/assignment/destructor logic is required.
If your class manages resources (like dynamic memory), then it now needs all 3 of them:

class Foo{
private:
    int* data
public:
/*normal ctor*/
    Foo(int d) : data( new int(d) ) {}; //the default would not initialize data at all, we want to allocate some memory
/*copy ctor*/
    Foo(const &Foo copy) : data( new int(*copy.data) ){};
   // the default would be: Foo(const &Foo copy) : data(copy.data)
   // we instead want to allocate a new int, and copy the other's value into our new one
/*assignment op*/
    Foo& operator=(const &Foo copy) // the default would be: data = copy.data; return *this;
    {//we don't want copy the address/pointers, we need to copy the value located at one pointer to the variable pointed to by the other
        *data = *copy.data;
        return *this;
    }
/*destructor*/
    ~Foo() { delete data; } //the default would do nothing, we need to clean up our memory allocation
};

The above custom implementations ensure that every Foo owns it's own int (that is created with new) and also cleans up after itself (using delete in the destructor).
If you let the compiler implement those 3 functions for you, it would simply copy the pointer around the place, and multiple Foo objects would end up sharing the same int object.

Foo *_ptr = new Foo();
/*does this create a whole new space in memory for test?
  if it does, am I correct in saying that the default assign op
  only makes test point to the same area of memory as _ptr - hence the whole
  deal about copy assignment operators?*/
Foo test = *_ptr;

This is a whole different question, and shows you need to first learn about stack vs heap allocations (or automatic storage vs free-store storage in strict C++ terminology).

When you write: int foo = 42; the variable 'foo' is created in the call-stack. It's a "temporary variable" that only exists for the lifetime of the function. When the function returns, 'foo' disappears.

When you write int* foo = new int(42), the variable 'foo' is a temporary on the call-stack as before, but this variable just stores the address of a memory allocation elsewhere. It's your responsibility to ensure that memory allocation is cleaned up with delete.

You can use complex types instead of int and everything works the same, except that when you create the variable, the constructor is called, and when the function returns, the destructor is called.

void test1()
{
  Foo test;//memory allocated on the stack, constructor is called;
  return;//destructor is called, stack is unwound
}
void test2()
{
  Foo* test;//memory for 'test' (the address/pointer variable) on the stack.
  test = new Foo();//Memory for a Foo object created on the heap, constructor is called, address of allocation stored in test
  delete test;//destructor called, heap allocation is cleaned up
  return;//stack is unwound
}
void test3(Foo* input)//this argument contains the address of a Foo object
{
  Foo test;//default constructor called, object exists on the stack
  test = *input;//assignment operator is called, which should copy the values located at the input allocation into the stack allocation at &test
  return;//test's destructor is called
}

#2Hodgman

Posted 05 February 2013 - 09:01 PM

However, I (think) I still don't understand the uses of the copy ctor/assignment op;

class Foo{
private:
    int data
public:
/*normal ctor*/
    Foo(int d) : data(d) {};
/*copy ctor*/
    Foo(const &Foo copy) : data(copy.data){};
/*assignment op*/
    Foo& operator=(const &Foo copy){ data = copy.data };
};
is the above correct?

Well, it behaves as you'd expect... except the assignment operator should be:
Foo& operator=(const &Foo copy){ data = copy.data; return *this; };
It breaks the rule of 3 though -- either you should have 0 of the 3 things in the rule, or 3 of the 3 things. You've only written 2 of them. The rule is: if you have a copy-ctor, assignment-op or destructor, you should have all three of them.

However, the copy-ctor and assignment-op aren't needed for such a simple class -- the compiler will generate them if you don't write them yourself, so that class should just be:

class Foo{
    int data
public:
    Foo(int d) : data(d) {};
};

This also satifies the rule, because you have 0 out of 3 of those things now.

if so, what do they have to do with dynamically allocated memory?

In your example, the only member is an int, so no custom copy/assignment/destructor logic is required.
If your class manages resources (like dynamic memory), then it now needs all 3 of them:

class Foo{
private:
    int* data
public:
/*normal ctor*/
    Foo(int d) : data( new int(d) ) {}; //the default would not initialize data at all
/*copy ctor*/
    Foo(const &Foo copy) : data( new int(copy.data) ){}; // the default would be ...: data(copy.data)
/*assignment op*/
    Foo& operator=(const &Foo copy) // the default would be: data = copy.data; return *this;
    {//don't copy the address/pointers, copy the value located at one pointer to the variable pointed to by the other
        *data = *copy.data;
        return *this;
    }
/*destructor*/
    ~Foo() { delete data; } //the default would do nothing
};

The above custom implementations ensure that every Foo owns it's own int (that is created with new) and also cleans up after itself (using delete in the destructor).
If you let the compiler implement those 3 functions for you, it would simply copy the pointer around the place, and multiple Foo objects would end up sharing the same int object.

Foo *_ptr = new Foo();
/*does this create a whole new space in memory for test?
  if it does, am I correct in saying that the default assign op
  only makes test point to the same area of memory as _ptr - hence the whole
  deal about copy assignment operators?*/
Foo test = *_ptr;

This is a whole different question, and shows you need to first learn about stack vs heap allocations (or automatic storage vs free-store storage in strict C++ terminology).

When you write: int foo = 42; the variable 'foo' is created in the call-stack. It's a "temporary variable" that only exists for the lifetime of the function. When the function returns, 'foo' disappears.

When you write int* foo = new int(42), the variable 'foo' is a temporary on the call-stack as before, but this variable just stores the address of a memory allocation elsewhere. It's your responsibility to ensure that memory allocation is cleaned up with delete.

You can use complex types instead of int and everything works the same, except that when you create the variable, the constructor is called, and when the function returns, the destructor is called.

void test1()
{
  Foo test;//memory allocated on the stack, constructor is called;
  return;//destructor is called, stack is unwound
}
void test2()
{
  Foo* test;//memory for 'test' (the address/pointer variable) on the stack.
  test = new Foo();//Memory for a Foo object created on the heap, constructor is called, address of allocation stored in test
  delete test;//destructor called, heap allocation is cleaned up
  return;//stack is unwound
}
void test3(Foo* input)//this argument contains the address of a Foo object
{
  Foo test;//default constructor called, object exists on the stack
  test = *input;//assignment operator is called, which should copy the values located at the input allocation into the stack allocation at &test
  return;//test's destructor is called
}

#1Hodgman

Posted 05 February 2013 - 08:57 PM

However, I (think) I still don't understand the uses of the copy ctor/assignment op;

class Foo{
private:
    int data
public:
/*normal ctor*/
    Foo(int d) : data(d) {};
/*copy ctor*/
    Foo(const &Foo copy) : data(copy.data){};
/*assignment op*/
    Foo& operator=(const &Foo copy){ data = copy.data };
};
is the above correct?


Well, it behaves as you'd expect... except the assignment operator should be:
Foo& operator=(const &Foo copy){ data = copy.data; return *this; };
It breaks the rule of 3 thoough -- either you should have 0 of the 3 things in the rule, or 3 of the 3 things. You've only written 2 of them. The rule is: if you have a copy-ctor, assignment-op or destructor, you should have all three of them.

However, the copy-ctor and assignment-op aren't needed for such a simple class -- the compiler will generate them if you don't write them yourself, so that class should just be:
class Foo{
    int data
public:
    Foo(int d) : data(d) {};
};
This also satifies the rule, because you have 0 out of 3 of those things now.

if so, what do they have to do with dynamically allocated memory?

In your example, the only member is an int, so no custom copy/assignment/destructor logic is required.
If your class manages resources (like dynamic memory), then it now needs all 3 of them:
class Foo{
private:
    int* data
public:
/*normal ctor*/
    Foo(int d) : data( new int(d) ) {};
/*copy ctor*/
    Foo(const &Foo copy) : data( new int(copy.data) ){};
/*assignment op*/
    Foo& operator=(const &Foo copy)
    {
        *data = *copy.data;
        return *this;
    }
/*destructor*/
    ~Foo() { delete data; }
};
The above custom implementations ensure that every Foo owns it's own int (that is created with new) and also cleans up after itself (using delete in the destructor).
If you let the compiler implement those 3 functions for you, it would simply copy the pointer around the place, and multiple Foo objects would end up sharing the same int object.


Foo *_ptr = new Foo();
/*does this create a whole new space in memory for test?
  if it does, am I correct in saying that the default assign op
  only makes test point to the same area of memory as _ptr - hence the whole
  deal about copy assignment operators?*/
Foo test = *_ptr;


This is a whole different question, and shows you need to first learn about stack vs heap allocations (or automatic storage vs free-store storage in strict C++ terminology).

When you write: int foo = 42; the variable 'foo' is created in the call-stack. It's a "temporary variable" that only exists for the lifetime of the function. When the function returns, 'foo' disappears.

When you write int* foo = new int(42), the variable 'foo' is a temporary on the call-stack as before, but this variable just stores the address of a memory allocation elsewhere. It's your responsibility to ensure that memory allocation is cleaned up with delete.

You can use complex types instead of int and everything works the same, except that when you create the variable, the constructor is called, and when the function returns, the destructor is called.
void test1()
{
  Foo test;//memory allocated on the stack, constructor is called;
  return;//destructor is called, stack is unwound
}
void test2()
{
  Foo* test;//memory for 'test' (the address/pointer variable) on the stack.
  test = new Foo();//Memory for a Foo object created on the heap, constructor is called, address of allocation stored in test
  delete test;//destructor called, heap allocation is cleaned up
  return;//stack is unwound
}
void test3(Foo* input)//this argument contains the address of a Foo object
{
  Foo test;//default constructor called, object exists on the stack
  test = *input;//assignment operator is called, which should copy the values located at the input allocation into the stack allocation at &test
  return;//test's destructor is called
}

PARTNERS