Destructor called when objects are passed by value

后端 未结 3 941
执笔经年
执笔经年 2021-02-14 06:00

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

3条回答
  •  灰色年华
    2021-02-14 06:50

    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 ptr;
         Sample(int i)
         {
             ptr = std::unique_ptr(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(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 dynamic ptr 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 ptr;
         Sample(int i)
         {
             ptr = make_shared(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 .

提交回复
热议问题