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

时间:2016-09-15 09:30:20

标签: c++ templates c++11

在下面的代码中,f()函数可以为不完整的operator bool()调用operator *()unique_ptr<C>成员函数class C。但是,当函数g()尝试为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 {};

2 个答案:

答案 0 :(得分: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
}

问题是,当我们对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,这就是为什么一个正常工作的原因。

有关详细信息,请参阅this answerCWG 557

答案 1 :(得分:5)

不是需要完整类型的unique_ptr,而是您的班级X

std::unique_ptr<C> pC;

您实际上还没有为C进行任何分配,因此编译器不需要在此处了解C的具体内容。

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

在此,您使用C作为X的模板类型。由于X包含类型为T的对象,C,因此编译器需要知道在X实例化时要分配的内容。 (t是一个对象,因此在构造时实例化)。将T t;更改为T* t;,编译器就不会抱怨。

修改

  

这并不能解释为什么h()编译,但g()不编译。

This example编译良好:

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

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