Home > Software engineering >  Strange behavior between `std::make_unique` and `std::unique_ptr` with forward declaration
Strange behavior between `std::make_unique` and `std::unique_ptr` with forward declaration

Time:06-13

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?

CodePudding user response:

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.

CodePudding user response:

When you have std::make_unique<View>(shared_from_this()), the compiler needs to instantiate the specialization of the function template template<class T, class... Args> std::unique_ptr<T> std::make_unique(Args&&...); with T = View, Args = {<empty>}. This happens at a "point of instantiation" of the specialization. There is one where it is used (right when you call make_unique), and also one at the end of the translation unit. Your compiler happens to instantiate it at the the end of the TU (as most compilers do), so it happens to "work", but it's actually ill-formed (since the compiler could have instantiated when it was used and it would have failed).

The reason std::unique_ptr<View>(new View(shared_from_this())) doesn't work is because the error is with the expression new View(shared_from_this()). There is no template function or point of instantiation to deal with, so the compiler has to complain immediately that View is incomplete, as expected.

The solution is to delay the definition of the function until View is complete:

struct View;

struct Database : public std::enable_shared_from_this<Database>
{
    // ...
    std::unique_ptr<View> GetView();
    // ...
};

struct View
{
    // ...
};


inline std::unique_ptr<View> Database::GetView()
{
    // View is complete here, OK
    return std::unique_ptr<View>(new View(shared_from_this()));
}
  • Related