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