为什么not_null还没有进入C++标准?

Why hasn't not_null made it into the C++ standard yet?

提问人:Bruce Adams 提问时间:5/7/2022 最后编辑:Bruce Adams 更新时间:10/17/2023 访问量:1654

问:

第 N 次将注释添加到原始指针后,我再次想知道模板发生了什么。"// not null"not_null

C++核心指南是很久以前创建的,并且已经有一些东西进入了标准,例如(有些类似于并起源于核心指南本身,但有时被混为一谈)。鉴于它相对简单,为什么not_null(或类似的东西)还没有进入标准?std::spanstring_viewstd::array

我定期扫描 ISO 邮件(但可能不彻底),我什至不知道有什么建议。


可能回答我自己的问题。我不记得遇到过任何情况,它可以防止我所处理的代码中的错误,因为我们尽量不以这种方式编写代码。

指南本身非常受欢迎,例如将其变成 clang-tidy 和声纳。支持库似乎不太受欢迎。

例如,boost 从一开始就作为 Linux 上的软件包提供。我不知道GSL的任何实现。不过,我认为它与Windows上的Visual C++捆绑在一起。


由于人们在评论中问过。

就我自己而言,我会用它来记录意图。 像这样的结构可能具有注释所没有的语义价值。 执行它是次要的,尽管我可以看到它的位置。这最好是在零开销的情况下完成的(也许只在编译时用于有限数量的情况)。not_null<>

我主要考虑的是原始指针成员变量的情况。我忘记了将指针传递给函数的情况,我总是使用引用来表示非 null,也表示“我没有所有权”。

同样(对于类成员),我们也可以记录所有权。owned<>not_owned<>

我想是否允许更改关联的对象。不过,这可能太高了。您可以使用引用成员而不是指针来记录这一点。我自己避免引用成员,因为我几乎总是想要可复制和可分配的类型。但是,请参阅例如,我应该在成员数据中更喜欢指针还是引用?,以获取有关此内容的一些讨论。

另一个维度是另一个实体是否可以修改变量。 “const”表示我保证不修改它。在多线程代码中,我们想说的几乎相反。那是“其他代码承诺在我们使用它时不会修改它”(没有显式锁),但这离题了......

C++ CPP 核心指南

评论

3赞 Yksisarvinen 5/7/2022
我正在尝试为此考虑一个用例。我觉得我更愿意拼写。not_null<int*>int&
2赞 Nicol Bolas 5/7/2022
@Yksisarvinen:“我正在尝试为此想出一个用例。我觉得我更愿意用 int& 拼写not_null<int*>。这不适用于 .其要点是它适用于智能指针或任何可为 null 的类型(即不是仅移动的)。not_null<std::shared_ptr<T>>not_null
1赞 user4581301 5/7/2022
如果指针可以更改,则引用将不起作用。
2赞 StoryTeller - Unslander Monica 5/7/2022
有趣的是,没有人(到目前为止发表评论)曾经编程过编码标准,要求输出参数由非拥有的原始指针传递(这有好处)。所以是的,存在注释和断言,但标准实用程序是更好的词汇表。
1赞 Nicol Bolas 5/7/2022
@MartinYork:“我什至会把参数看作是反模式,并期望函数返回目标。在很多情况下,您仍然需要拥有合法的对象。在 a 或 anything 中返回多个值可能很麻烦,甚至会抑制省略。tuple

答:

19赞 Nicol Bolas 5/7/2022 #1

有一个大的技术问题可能无法解决,这使得标准化成为一个问题:它不能与仅移动的智能指针一起使用。not_null

最重要的用例是使用智能指针(对于原始指针,引用通常就足够了,但即便如此,有时引用也不起作用)。 是一个有用的东西,它说明了使用此类对象的 API 的一些重要信息。not_nullnot_null<shared_ptr<T>>

但不起作用。它不能工作。原因是从唯一指针移动会使旧对象为 null。这正是预期要防止的。因此,始终在其包含的 .哪。。。你不能用 ,因为这是该类型的重点。not_null<unique_ptr<T>>not_nullnot_null<T>Tunique_ptr

能够说 API 消耗的 is not null 是好的和有用的。但是你实际上不能用 来做到这一点,这会给它的效用带来一个漏洞。unqiue_ptrnot_null

只要仅移动的智能指针不能使用 ,标准化类就会成为问题。not_null

评论

0赞 Martin York 5/7/2022
但这个问题是关于 RAW 指针的。但是关于not_null本身发生了什么的很好的答案。
1赞 NathanOliver 5/7/2022
@MartinYork 是吗?标题和 bosy 都问:鉴于它相对简单,为什么 not_null(或类似的东西)还没有进入标准? 这个答案很好。
0赞 Martin York 5/7/2022
@NathanOliver 是的,这是真的。我太纠结于OP为什么问了,我忽略了实际的问题。
0赞 Bruce Adams 5/7/2022
这假定原始指针的not_null本身没有用。为什么它需要适用于所有可能的情况?难道我们不能not_null<unique_ptr<>>导致静态断言失败吗?
0赞 Nicol Bolas 5/7/2022
@BruceAdams:“这假定原始指针的not_null本身没有用。你的意思是,除了我承认那里也有有用的案例的部分?“为什么它需要适用于所有可以想象到的情况?”因为如果它不具有普遍性,它就没有发挥作用。重点是消除歧义。如果它留下了歧义,那么你必须依靠其他方法。所以你不妨使用总是有效的东西。
1赞 c z 10/12/2023 #2

Not-null除了拥有指针之外,在任何事情上都没有意义。对于无主数据,我们已经有了引用 (),或者对于可变情况,引用包装器 ()。int&std::reference_wrapper<int>

我自己尝试了一个非空指针的 MWE,并很快意识到它不适用于一般情况:

#include <iostream>
#include <memory>

template<typename T>
class NotNull
{
    std::unique_ptr<T> _ptr;

public:
    template<typename...U>
    NotNull(U...args) : _ptr(std::make_unique<T>(std::forward<U>(args)...))
    {}

    NotNull(NotNull<T> &&moveFrom) noexcept : _ptr(std::move(moveFrom._ptr))
    { AssertNotNull(); }

    NotNull<T> &operator=(NotNull<T> &&moveFrom) noexcept
    { _ptr = std::move(moveFrom._ptr); }

    void AssertNotNull()
    { if (!_ptr) throw std::runtime_error("Attempt to copy null."); }

    T &operator*()
    { AssertNotNull(); return *_ptr; }

    T *operator->()
    { AssertNotNull(); return _ptr.get(); }
};

int main()
{
    NotNull<int> a(42);
    std::cout << *a << "\n";
    NotNull<int> b = std::move(a);
    std::cout << *b;
    std::cout << *a << "\n"; // throws
    return 0;
}
  • 我们不能有一个空值的构造函数......
  • ...但是当我们移动对象时,我们会得到一个 null 值......
  • ...所以现在我们的类到处都需要讨厌的运行时断言。这揭示了问题:
    • 我们是否应该避免冗余(连续)检查?什么时候?
    • 我们是否应该将检查设为条件(例如调试模式)?
  • 这开辟了太多的途径,这让我们回到了仅仅使用 .然后,用户可以为其特定案例提供自己的检查或包装器。unique_ptr

此外,我们在这里实际检查的是移动后使用,大多数 IDE 倾向于将其标记为问题。

也就是说,如果是为了代码的文档,Stroustrup 已经为此目的提出了建议(在这种情况下表示所有权;请参阅此 SO 答案)。我们可以很容易地写出:using

template<typename T> using notnull_ptr = std::unique_ptr<T>;