Search code examples
c++c++11c++17shared-memoryunique-ptr

Strange behavior between `std::make_unique` and `std::unique_ptr` with forward declaration


std::make_unique<T> needs C++ 17 feature.It's a pity that I have to use C++11. When I am porting the code snippet to C++11, I found a strange thing.

The code snippet which uses make_unique works well:

#include <iostream>
#include <memory>
 
struct View;

struct Database : public std::enable_shared_from_this<Database>
{
    static std::shared_ptr<Database> Create(){ return std::shared_ptr<Database>(new Database());}

    std::unique_ptr<View> GetView() { return std::make_unique<View>(shared_from_this()); } //works well

    ~Database() {std::cout << "Database is destoryed" << std::endl;}
private:
        Database(){};
};

struct View
{
    std::shared_ptr<Database> db;
    View(std::shared_ptr<Database> db) : db(std::move(db)) {}
    ~View() {std::cout << "View is destoryed" << std::endl;}
};

int main()
{
    std::shared_ptr<View> view;
    {
        auto db{Database::Create()} ;
        view = db->GetView();
    }
}

whereas the code snippet below does not compile:

#include <iostream>
#include <memory>
 
struct View;

struct Database : public std::enable_shared_from_this<Database>
{
    static std::shared_ptr<Database> Create(){ return std::shared_ptr<Database>(new Database());}

    std::unique_ptr<View> GetView() { return std::unique_ptr<View>(new View(shared_from_this())); }  //here is the modification
    ~Database() {std::cout << "Database is destoryed" << std::endl;}
private:
        Database(){};
};

struct View
{
    std::shared_ptr<Database> db;
    View(std::shared_ptr<Database> db) : db(std::move(db)) {}
    ~View() {std::cout << "View is destoryed" << std::endl;}
};

int main()
{
    std::shared_ptr<View> view;
    {
        auto db{Database::Create()} ;
        view = db->GetView();
    }
}

Here is what the complier complains:

<source>: In member function 'std::unique_ptr<View> Database::GetView()':
<source>:10:95: error: invalid use of incomplete type 'struct View'
   10 |     std::unique_ptr<View> GetView() { return std::unique_ptr<View>(new View(shared_from_this())); }  //here is the modification
      |                                                                                               ^
<source>:4:8: note: forward declaration of 'struct View'
    4 | struct View;
      |        ^~~~

After I did some modification for the second code snippet,this one works:

#include <iostream>
#include <memory>

struct Database;
 
struct View
{
    std::shared_ptr<Database> db;
    View(std::shared_ptr<Database> db) : db(std::move(db)) {}
    ~View() {std::cout << "View is destoryed" << std::endl;}
};

struct Database : public std::enable_shared_from_this<Database>
{
    static std::shared_ptr<Database> Create(){ return std::shared_ptr<Database>(new Database());}
    #if 0
    std::unique_ptr<View> GetView() { return std::make_unique<View>(shared_from_this()); } //works well
    #else
    std::unique_ptr<View> GetView() { return std::unique_ptr<View>(new View(shared_from_this())); }
    #endif
    ~Database() {std::cout << "Database is destoryed" << std::endl;}
private:
        Database(){};
};


int main()
{
    std::shared_ptr<View> view;
    {
        auto db{Database::Create()} ;
        view = db->GetView();
    }
}

Why std::make_unique<View>(shared_from_this()) works even if there is only a forward delaration for View before Database's definition, whereas the compiler complains about std::unique_ptr<View>(new View(shared_from_this()) under the same condition?


Solution

  • Why std::make_unique<View>(shared_from_this()) works even if there is only a forward delaration for View before Database's definition, whereas the compiler complains about std::unique_ptr<View>(new View(shared_from_this()) under the same condition?

    Consider this simplified example:

    #include <memory>
    
    struct foo;
    
    std::unique_ptr<foo> make_foo_1() { return std::make_unique<foo>(); }       // OK
    std::unique_ptr<foo> make_foo_2() { return std::unique_ptr<foo>(new foo); } // ERROR
    
    struct foo {};
    

    In make_foo_1, std::unique_ptr<foo> is made a dependent type in make_unique<foo> which means that it'll postpone binding to unique_ptr<foo>.

    But "Non-dependent names are looked up and bound at the point of template definition" (i.e., the definition of std::unique_ptr<foo>) which means that, in make_foo_2, the definition of foo must have already been seen by the compiler or else it'll complain about foo being an incomplete type.