有前向声明的std::make_unique和std::unique_ptr之间的奇怪行为



std::make_unique<T>需要C++ 17的特性。很遗憾,我不得不使用C++11。当我将代码片段移植到C++11时,我发现了一件奇怪的事情。

使用make_unique的代码片段效果很好:

#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();
}
}

,而下面的代码片段不能编译:

#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();
}
}

编译器报错:

<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;
|        ^~~~

在我对第二个代码片段做了一些修改之后,这个代码可以工作了:

#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();
}
}

即使在Database的定义之前只有View的前向声明,std::make_unique<View>(shared_from_this())也能工作,而在相同的条件下,编译器会抱怨std::unique_ptr<View>(new View(shared_from_this())?

为什么std::make_unique<View>(shared_from_this())即使在Database定义之前只有View的前向声明也能工作,而编译器在相同的条件下抱怨std::unique_ptr<View>(new View(shared_from_this())?

考虑这个简化的例子:

#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 {};

make_foo_1中,std::unique_ptr<foo>make_unique<foo>中成为依赖类型,这意味着它将延迟绑定到unique_ptr<foo>

但是"非依赖名称被查找并在模板定义时绑定">(即std::unique_ptr<foo>的定义),这意味着,在make_foo_2中,foo的定义必须已经被编译器看到,否则它会抱怨foo是一个不完整的类型。

当您有std::make_unique<View>(shared_from_this())时,编译器需要用T = View, Args = {<empty>}实例化函数模板template<class T, class... Args> std::unique_ptr<T> std::make_unique(Args&&...);的专门化。这发生在"实例化点"。专业化的。有一个地方使用它(就在调用make_unique时),还有一个在翻译单元的末尾。你的编译器碰巧在TU的末尾实例化它(就像大多数编译器一样),所以它碰巧"工作",但它实际上是错误的(因为编译器可能在使用它的时候实例化了,它可能会失败)。

std::unique_ptr<View>(new View(shared_from_this()))不工作的原因是错误与表达式new View(shared_from_this())。没有模板函数或实例化点需要处理,因此编译器必须立即报错View不完整,正如预期的那样。

解决方案是延迟函数的定义,直到View完成:

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()));
}

最新更新