【问题标题】:When does unique_ptr require a complete type?unique_ptr 什么时候需要一个完整的类型?
【发布时间】:2016-09-15 09:30:20
【问题描述】:

在下面的代码中,函数f()可以调用unique_ptr<C>的成员函数operator bool()operator *(),用于不完整的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 {};

【问题讨论】:

  • 我在 Visual Studio 2013 中编译您的代码没有问题。您使用什么编译器?
  • 我使用 Visual Studio 2015、GNU 和 Clang 进行了测试。
  • 如果单独使用每行带有“错误”的代码是否都会出错?还是只有在同一个函数中有两个时才会出错?
  • 是的,每一行都有一个错误。
  • @Barry 不幸的是,您链接为重复的问题中提供的答案超出了我的想象。我只是打算使用解决方法(如h())并希望我不会以某种方式被烧毁。

标签: c++ templates c++11


【解决方案1】:

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

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&lt;T&gt;::foo() 时,我们触发了依赖于参数的查找。

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

回到最初的问题,对!pX*pX 的调用会调用ADL,这会导致X&lt;C&gt; 的实例化,这是格式错误的。对pX.get() 的调用确实调用ADL,这就是为什么它可以正常工作的原因。


更多详情请参阅this answer,另请参阅CWG 557

【讨论】:

  • 那我们为什么不也必须在 !pC 处进行重载解析呢?此时,C也不完整。
  • @RichardHodges 我们确实对!pC 进行了重载解析。里面没有任何东西需要C 是完整的——它不是一个模板(不会被实例化)。 !pX 需要对格式错误的 X&lt;C&gt; 进行实例化。
  • 好吧,曙光初现……为什么需要实例化 X?没有什么取决于它。在应用 operator! 时,标准的哪一部分优先于 ADL 而不是隐式转换为 bool - 给定该运算符!是隐式布尔上下文中的特例吗?
  • @RichardHodges 重载解析的第 1 步是查找所有名称。查找名称会调用 ADL。 ADL 触发实例化。
  • 知道了。非常感谢。 ^^ 这应该是答案。特别提一下,根据一些运营商的特殊规定。 cppreference 页面在其他方面具有误导性。
【解决方案2】:

需要完整类型的不是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)中的错误。

【讨论】:

  • 这个解释没有多大意义,如果编译器“需要知道分配什么”那么CX&lt;C&gt;一样是个问题
  • @M.M 正如 Ivan 所说,只要具有模板实例化的类使用T 类型的直接对象(unique_ptr 使用T*,X确实使用一个)然后它很好而且花花公子。
  • @IvanRubinson 这有什么关系?
  • 这并不能解释为什么h() 可以编译,而g() 不能。
  • @M.M unique_ptr 可以采用不完整的类型,因为它只有指向这些类型的指针。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-12-15
  • 2018-12-10
  • 2015-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-01
相关资源
最近更新 更多