unique_ptr什么时候需要完整的类型?

Bar*_*ett 10 c++ templates c++11

在下面的代码中,函数f()可以调用operator bool()和不完整的operator *()成员函数.但是当函数试图调用那些相同的成员函数时,编译器突然想要一个完整的类型并尝试实例化,然后失败.由于某种原因,不会导致模板实例化并正确编译,如功能中所示.这是为什么?有什么不同和?unique_ptr<C>class Cg()unique_ptr<X<C>>X<C>unique_ptr<X<C>>::get()h()get()operator bool()operator *()

#include <memory>

class C;
std::unique_ptr<C> pC;

C& f() {
    if ( !pC ) throw 0; // OK, even though C is incomplete
    return *pC;         // OK, even though C is incomplete
}

template <class T>
class X
{
    T t;
};

std::unique_ptr<X<C>> pX;

X<C>& g() {
    if ( !pX ) throw 0; // Error: 'X<C>::t' uses undefined class 'C'
    return *pX;         // Error: 'X<C>::t' uses undefined class 'C'
}

X<C>& h() {
    if ( !pX.get() ) throw 0; // OK
    return *pX.get();         // OK
}

class C {};
Run Code Online (Sandbox Code Playgroud)

Bar*_*rry 7

这是一个人为的简化示例,仅使用我们自己的类型:

class Incomplete;

template <class T>
struct Wrap {
    T t;
};

template <class T>
struct Ptr {
    T* p;

    void foo() { }
};

template <class T>
void foo(Ptr<T> ) { }

int main() {
    Ptr<Incomplete>{}.foo();         // OK
    foo(Ptr<Incomplete>{});          // OK

    Ptr<Wrap<Incomplete>>{}.foo();   // OK
    ::foo(Ptr<Wrap<Incomplete>>{});  // OK!
    foo(Ptr<Wrap<Incomplete>>{});    // error
}
Run Code Online (Sandbox Code Playgroud)

问题是,当我们进行非限定调用时foo,而不是对::foo成员函数的限定调用Ptr<T>::foo(),我们将触发与参数相关的查找.

ADL将在类模板特化中查找模板类型的关联命名空间,这将触发隐式模板实例化.需要触发模板实例化以执行ADL查找,因为例如Wrap<Incomplete>可以声明friend void foo(Ptr<Wrap<Incomplete >> )需要调用的模板实例.或者Wrap<Incomplete>可能有依赖基础,其名称空间也需要考虑.此时的实例化会使代码格式错误,因为它Incomplete是一个不完整的类型,并且您不能拥有不完整类型的成员.

回到最初的问题,调用!pX*pX调用ADL导致其瞬间X<C>变形.调用pX.get()没有调用ADL,这就是为什么一个能正常工作.


有关详细信息,请参阅此答案,CWG 557.

  • @RichardHodges我们做`!pC`的重载解析.那里没有任何东西要求`C`完成 - 它不是模板(不会被实例化).`!pX`需要实例化`X <C>`,这是不正确的. (2认同)
  • @RichardHodges重载决议的第1步是找到所有的名字.查找名称会调用ADL.ADL触发实例化. (2认同)

Som*_*ken 5

这不是unique_ptr需要完整类型,而是你的班级X.

std::unique_ptr<C> pC;
Run Code Online (Sandbox Code Playgroud)

你实际上还没有进行任何分配,C所以编译器不需要知道C这里的具体细节.

std::unique_ptr<X<C>> pX;
Run Code Online (Sandbox Code Playgroud)

在这里,您将其C用作模板类型X.由于X包含类型的对象,T这是C这里的编译器需要知道什么时候分配什么X被实例化.(t是一个对象,因此在构造时实例化).更改T t;T* t;,编译器不会抱怨.

编辑:

这并不能解释h()编译的原因,但g()却没有.

这个例子很好编译:

#include <memory>

class C;
std::unique_ptr<C> pC;

C& f() {
    if (!pC) throw 0; // OK, even though C is incomplete
    return *pC;         // OK, even though C is incomplete
}

template <class T>
class X
{
    T t;
};

std::unique_ptr<X<C>> pX;

typename std::add_lvalue_reference<X<C>>::type DoSomeStuff() // exact copy of operator*
{
    return (*pX.get());
}

void g() {
    if ((bool)pX) return;
}

class C {};

int main()
{
    auto z = DoSomeStuff();
}
Run Code Online (Sandbox Code Playgroud)

这使得它更有趣,因为这模仿了operator*,但进行编译.!从表达式中删除也有效.这似乎是多个实现中的错误(MSVC,GCC,Clang).

  • 这并不能解释为什么`h()`编译,但`g()`却没有. (7认同)