提问人:Bruce Adams 提问时间:5/7/2022 最后编辑:Bruce Adams 更新时间:10/17/2023 访问量:1654
为什么not_null还没有进入C++标准?
Why hasn't not_null made it into the C++ standard yet?
问:
第 N 次将注释添加到原始指针后,我再次想知道模板发生了什么。"// not null"
not_null
C++核心指南是很久以前创建的,并且已经有一些东西进入了标准,例如(有些类似于并起源于核心指南本身,但有时被混为一谈)。鉴于它相对简单,为什么not_null(或类似的东西)还没有进入标准?std::span
string_view
std::array
我定期扫描 ISO 邮件(但可能不彻底),我什至不知道有什么建议。
可能回答我自己的问题。我不记得遇到过任何情况,它可以防止我所处理的代码中的错误,因为我们尽量不以这种方式编写代码。
指南本身非常受欢迎,例如将其变成 clang-tidy 和声纳。支持库似乎不太受欢迎。
例如,boost 从一开始就作为 Linux 上的软件包提供。我不知道GSL的任何实现。不过,我认为它与Windows上的Visual C++捆绑在一起。
由于人们在评论中问过。
就我自己而言,我会用它来记录意图。
像这样的结构可能具有注释所没有的语义价值。
执行它是次要的,尽管我可以看到它的位置。这最好是在零开销的情况下完成的(也许只在编译时用于有限数量的情况)。not_null<>
我主要考虑的是原始指针成员变量的情况。我忘记了将指针传递给函数的情况,我总是使用引用来表示非 null,也表示“我没有所有权”。
同样(对于类成员),我们也可以记录所有权。owned<>
not_owned<>
我想是否允许更改关联的对象。不过,这可能太高了。您可以使用引用成员而不是指针来记录这一点。我自己避免引用成员,因为我几乎总是想要可复制和可分配的类型。但是,请参阅例如,我应该在成员数据中更喜欢指针还是引用?,以获取有关此内容的一些讨论。
另一个维度是另一个实体是否可以修改变量。 “const”表示我保证不修改它。在多线程代码中,我们想说的几乎相反。那是“其他代码承诺在我们使用它时不会修改它”(没有显式锁),但这离题了......
答:
有一个大的技术问题可能无法解决,这使得标准化成为一个问题:它不能与仅移动的智能指针一起使用。not_null
最重要的用例是使用智能指针(对于原始指针,引用通常就足够了,但即便如此,有时引用也不起作用)。 是一个有用的东西,它说明了使用此类对象的 API 的一些重要信息。not_null
not_null<shared_ptr<T>>
但不起作用。它不能工作。原因是从唯一指针移动会使旧对象为 null。这正是预期要防止的。因此,始终在其包含的 .哪。。。你不能用 ,因为这是该类型的重点。not_null<unique_ptr<T>>
not_null
not_null<T>
T
unique_ptr
能够说 API 消耗的 is not null 是好的和有用的。但是你实际上不能用 来做到这一点,这会给它的效用带来一个漏洞。unqiue_ptr
not_null
只要仅移动的智能指针不能使用 ,标准化类就会成为问题。not_null
评论
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>;
评论
not_null<int*>
int&
not_null<std::shared_ptr<T>>
not_null
tuple