Is it true that a unique_ptr declaration, unlike a auto_ptr declaration, is well-defined when its template type is of an incomplete type?

后端 未结 2 1551
悲&欢浪女
悲&欢浪女 2021-01-11 13:54

I wrote this article and got some comments on it that confused me.

It basically boils down to my having seen T2 used only as a template parameter and mi

2条回答
  •  挽巷
    挽巷 (楼主)
    2021-01-11 14:20

    The following example is an attempt to demonstrate the difference between std::auto_ptr and std::unique_ptr. First consider this program consisting of 2 source files and 1 header:

    The header:

    // test.h
    
    #ifndef TEST_H
    #define TEST_H
    
    #include 
    
    template 
    using smart_ptr = std::auto_ptr;
    
    struct T2;
    
    struct T1
    {
        smart_ptr obj;
    
        T1(T2* p);
    };
    
    T2*
    source();
    
    #endif  // TEST_H
    

    First source:

    // test.cpp
    
    #include "test.h"
    
    int main()
    {
        T1 t1(source());
    }
    

    Second source:

    // test2.cpp
    
    #include "test.h"
    #include 
    
    
    struct T2
    {
        ~T2() {std::cout << "~T2()\n";}
    };
    
    T1::T1(T2* p)
        : obj(p)
    {
    }
    
    T2*
    source()
    {
        return new T2;
    }
    

    This program should compile (it may compile with a warning, but it should compile). But at run time it demonstrates undefined behavior. And it probably won't output:

    ~T2()
    

    which indicates that T2's destructor has not been run. At least it doesn't on my system.

    If I change test.h to:

    template 
    using smart_ptr = std::unique_ptr;
    

    Then the compiler is required to output a diagnostic (an error).

    That is, when you make this mistake with auto_ptr you get a run time error. When you make this mistake with unique_ptr you get a compile time error. And that is the difference between auto_ptr and unique_ptr.

    To fix the compile time error you must outline ~T1() after T2 is complete. In test2.cpp add after T2:

    T1::~T1() = default;
    

    Now it should compile and output:

    ~T2()
    

    You will likely want to declare and outline move members as well:

    T1::T1(T1&&) = default;
    T1& T1::operator=(T1&&) = default;
    

    You could make these same fixes with auto_ptr and it would again be correct. But again, the difference between auto_ptr and unique_ptr is that with the former, you don't find out until run time that you have some debugging to do (modulo optional warnings your compiler may give). With the latter you are guaranteed to find out at compile time.

提交回复
热议问题