Even though objects are passed to functions by means of the normal call-by-value parameter passing mechanism, which, in theory, protects and insulates the cal
Your object was copied field by field, so inside SomeFunc
you have two instances of Sample
- s1
and x
(but only x
is accessible), and value of x.ptr
is equal to s1.ptr
. Then when SomeFunc
ends, destructor is called and from that point s1.ptr
points to unallocated memory. Its called "dangling pointer".
I'll give an answer more from the Modern C++ perspective of "avoid raw pointers if you can". But I'll also point out an important distinction you should be aware of:
C++ constructor syntax
But first, let's consider what your intention is. If I wrote:
Sample x = 1;
Sample y = x;
What should be the semantics?
Should the
Sample
"copies" each have their own independent 'ptr', whose pointed-to object lifetime lasts only as long as the class they they live in?
It's usually the case that you do not need pointers at all if this is what you want.
Most of the time, the total class size will be reasonable enough that stack allocation won't be a problem if you are declaring them without new
(as you are here). So why get pointers involved? Just use int i
(or whatever your non-POD class is).
If you actually have the kind of case where you do need to dynamically allocate large blocks of data to manage yourself (vs. deferring to C++ library collections or similar), those might exceed your stack. If you need to dynamically allocate, you're going to need copy construction one way or another. That means Sample
will need to explicitly manage the copy construction -or- use a smart pointer class that finesses it so it doesn't have to.
First let's say you're keeping the raw pointer, that would mean:
Sample(const Sample & other)
{
ptr = new int(*other.ptr);
}
BUT you could reduce the potential for error in this situation by using a unique_ptr
instead. A unique_ptr will destroy the data pointed to by the raw pointer it's holding automatically when its destructor runs. So you don't have to worry about calling delete
.
Also, a unique_ptr will refuse to copy by default. Hence if you just wrote:
class Sample
{
public:
unique_ptr<int> ptr;
Sample(int i)
{
ptr = std::unique_ptr<int>(new int(i));
}
~Sample()
{
cout << "destroyed";
}
void PrintVal()
{
cout << "The value is " << *ptr;
}
};
The class itself can build, but you would get errors at your callsites. They would point out that you're making copies for something that copy construction has not been properly defined for. Not only that...you aren't just making one copy in your program, but two:
In function ‘int main()’:
error: use of deleted function ‘Sample::Sample(const Sample&)’
Sample s1 = 10;
^
note: ‘Sample::Sample(const Sample&)’ is implicitly deleted
because the default definition would be ill-formed:
error: use of deleted function ‘Sample::Sample(const Sample&)’
SomeFunc(s1);
^
That gives you a heads up to add a copy constructor equivalent to:
Sample(const Sample & other)
{
ptr = std::unique_ptr<int>(new int(*other.ptr));
}
Plus you probably want to change Sample s1 = 10;
to Sample s1 (10);
to avoid the copy there. For that matter, you may well have wanted SomeFunc
to take its values by reference as well. I'll also mention looking into initializer lists vs assignments.
(Note: There's actually a name for the pattern of a smart pointer class that copies called clone_ptr
, so you wouldn't have to write even that copy constructor. It's not in the standard C++ library but you'll find implementations around.)
Should the
Sample
"copies" share a common dynamicptr
that is deleted only after the last reference goes away?
Easier with smart pointers, and no copy constructor needed on Sample at all. Use a shared_ptr
. The default behavior of a shared_ptr is to be able to be copied with simple assignments.
class Sample
{
public:
shared_ptr<int> ptr;
Sample(int i)
{
ptr = make_shared<int>(i);
}
~Sample()
{
cout << "destroyed";
}
void PrintVal()
{
cout << "The value is " << *ptr;
}
};
Moral of the story is that the more you can let the default behaviors do the correct work for you...and the less code you write...the less potential you have for bugs. So while it's good to know what copy constructors do and when they are invoked, it can be equally important to know how to not write them!
Note that unique_ptr and shared_ptr are from C++11, and will require #include <memory>
.
This is indeed because you didn't provide a copy constructor. Thus the compiler will generate one for you, which does trivial copy. And that's the trivial copy of the pointer that's problematic here.
For the following declaration
void SomeFunc(Sample x);
There will be indeed a copy when you pass s1
to the function, but this copy will have a copy of the pointer, that is, the two object will point to the same int
.
Then, when exiting the function, the copy will be destroyed and will delete that pointer, leaving the original object in the calling code with a pointer just deleted (remember, they point to the same thing).
Then, for the following declaration
void SomeFunc(Sample &x);
you don't have any copy, thus the problem doesn't show up. Indeed, passing by reference means that inside the function, the Sample
object you're manipulating is exactly the same as the one you passed to the function, and won't be destroyed when the function exits.