使用默认参数在 optional 中unique_ptr 内部的类的正向声明失败

Forward declaration of class inside unique_ptr inside optional with default argument fails

提问人:Michael 提问时间:7/2/2023 最后编辑:Michael 更新时间:7/2/2023 访问量:136

问:

我有以下一段代码

// DoSomething.h
class Foo;
void doSomething(std::optional<std::unique_ptr<const Foo>> f = std::nullopt);

如果我在没有文件定义的情况下包含任何地方,则不会编译,即使它甚至没有调用该函数。相反,删除默认参数,一切都可以正常编译。DoSomething.hFoodoSomething=std::nullopt

我的假设是该问题与unique_ptr的删除器接口有关,我的问题是如何解决这个问题,以便我可以在这种情况下转发声明?Foo

谢谢

编辑:我不想更改此函数的签名,我知道我可以通过重载或删除可选用途来解决这个问题。我想知道为什么这不起作用。

以下注意 - 在切换 with 时,相同的代码编译得非常好。这意味着应该可以使其与 .我知道他们的删除器的界面略有不同,而且我对它不够熟悉。std::unique_ptrstd::shared_ptrstd::unique_ptr

C++ std可选

评论

2赞 n. m. could be an AI 7/2/2023
也许不要使用可选指针。指针已经具有带外值,因此请考虑改用它。nullptr
0赞 Jarod42 7/2/2023
“删除 =std::nullopt 默认参数,一切都编译良好”,所以你有一个解决方案,重载函数而不是默认参数。
0赞 Eljay 7/2/2023
如果你想要标准中的章节和诗句,你可能需要标签 为什么unique_ptr触发此行为。否则,我将省略默认参数值,并定义一个重载,其中实现仅使用参数调用另一个重载。language-lawyervoid doSomething();std::nullopt
0赞 Michael 7/2/2023
@n.m.willseey'allonReddit 我的代码示例不同,我创建了一个错误再次发生的最小示例。我知道可以编写不同的有效代码,但我不明白的是为什么这段代码不能,即使我从未使用过 .Foo
0赞 Michael 7/2/2023
也许可以转发声明该析构函数或我不知道的类似内容以避免错误,这就是我问问题的原因,我不想修改签名FoodoSomething

答:

2赞 chrysante 7/2/2023 #1

Cppreference 对 的模板参数说了以下几点:std::optional

T - 要管理其初始化状态的值的类型。该类型必须满足可破坏的要求

  • std::unique_ptr<T>不完整时不可破坏。T

  • 函数的默认参数在声明该函数时实例化。

因此,在实例化对象时不满足 的要求。std::optional

如注释中所述,您可以通过重载函数而不是使用默认参数来解决问题。此外,指针会公开一个 null 值,因此在大多数情况下不是必需的。std::optional<pointer-type>


阅读您的编辑后跟进:

class Foo;
using FooDeleter = std::function<void(Foo const*)>;
void doSomething(std::optional<std::unique_ptr<const Foo, FooDeleter>> f = std::nullopt);

此代码将正常编译。它与类似于 的已擦除删除程序类型一起使用。如果使用默认删除器()进行实例化,则问题是: 的简化定义如下所示:std::unique_ptrstd::shared_ptrunique_ptrstd::default_deletestd::default_delete

template <typename T>
class default_delete {
    void operator()(T* p) const {
        p->~T();
    }
};

所以在实例化过程中,上面的代码将被实例化。因为它对未声明的析构函数进行调用,编译将失败。 如果实例化,则只会调用 的 ,这与析构函数的定义无关。std::optional<std::unique_ptr<Foo>>Foostd::optional<std::unique_ptr<Foo, FooDeleter>>operator()std::function<...>Foo

评论

0赞 Michael 7/2/2023
谢谢你的回答。我不想更改函数的签名。这个确切的代码可以很好地编译shared_ptr,对于不完整的 s shared_ptr可破坏吗?T
1赞 chrysante 7/2/2023
是的,对于不完整是可破坏的.请注意,不像没有模板参数。它使用在构造和分配对象期间存储的类型擦除删除程序。shared_ptr<T>Tshared_ptrunique_ptrDeletershared_ptr
0赞 Michael 7/2/2023
但是,当unique_ptr存储为类的成员时,甚至在此示例中,当 被删除时,也可以转发声明 s for unique_ptr。这是标准限制吗?还是我应该定义别的东西?因为我看不出标准会限制我这样做的任何理由T= std::nullopt
0赞 chrysante 7/2/2023
只有当该类的析构函数(和移动操作)在定义 之后以行外方式定义时,才可以存储 的 正向声明为类成员。当你删除 时,在函数的声明中不会实例化任何类型的对象。在这种情况下,您只命名类型,该类型没有任何进一步的要求。unique_ptrTT= std::nulloptstd::optional<std::unique_ptr<Foo>>