【问题标题】:Trivial raw pointer that self-initializes to nullptr in C++在 C++ 中自初始化为 nullptr 的简单原始指针
【发布时间】:2013-07-06 05:34:39
【问题描述】:

我喜欢 C++11 中的新指针类型,但有时我仍然需要原始指针。然而,让我对 C++ 中的“原始”类型越来越难过的是,它们习惯于在未给出显式值时初始化为未定义。当我更频繁地使用 std::shared_ptr 等时,需要将原始指针初始化为 null 感觉越来越脆弱和不必要。我说的是:

class foo
{
    ...
    std::shared_ptr< bar > pb;   // Initially null in whatever constructor.
    std::unique_ptr< dar > pd;   // Likewise.
    std::weak_ptr< gar > pg;     // And again.
    lar* pr;                     // Uh-oh! Who knows what this is? Better remember to initialize...
};

foo::foo( int j )
: pr( nullptr )
{...}

foo::foo( const string& s )
: pr( nullptr )
{...}

... etc.: many tedious and error-prone constructor definitions follow.

因此,我想要的是“具有空初始化的原始指针”。比如:

class foo
{
    ...
    std::shared_ptr< bar > pb;   // Initially null in whatever constructor.
    std::unique_ptr< dar > pd;   // Likewise.
    std::weak_ptr< gar > pg;     // And again.
    raw_ptr< lar > pr;           // Once more with feeling.
};

foo::foo( int j )
{...}                            // No explicit pointer initialization necessary.

foo::foo( const string& s )
{...}

...

更准确地说,我想要的是一种简单、廉价的类型,除了它的默认构造函数将其初始化为 nullptr 之外,它在各方面的行为都与原始指针完全相同。

我的问题:(1)标准库中是否已经存在这样的东西? (2) 如果不是,那么实现这种类型的最优雅/最小的方法是什么?

附:我对 Boost 或任何其他库不感兴趣,除非它可能是单个文件中的仅标头库。小巧和简单至关重要。

【问题讨论】:

  • 你可以在类中初始化它:lar *pr{};.
  • 这看起来像是一个你如何充分利用错误的工具?类型的问题。首先,您需要澄清为什么解决实际问题的方法是使用原始指针。除非这是合理的,否则考虑到不初始化它的脆弱性是没有问题的。
  • @DavidRodríguez-dribeas 来吧。原始指针仍然有一个有效的位置。我认为原始指针的使用不需要合理化。鉴于此,它们明显不同于需要显式初始化的“现代”指针。我认为仅凭这些事实就可以提出问题。
  • @OldPeculier:相反,原始指针应该是例外,并且作为例外应该仅在需要特别小心处理的少数组件中使用。如果您正在处理这种类型的特殊组件并且您确实付出了额外的注意,那么您可能不会忘记初始化。当您认为原始指针很常见并且习惯了它们时,问题就出现了,您变得更加粗心。在一个原始指针很少见的世界里,带有原始指针的代码已经很可疑,并且不太可能出现绕过审查的错误。
  • @jmucchiello 好伤心。像这样的问题真的是讨论软件设计哲学和 C++ 使用的正确场所吗?人们以各种方式使用 C++。生活,让生活!出于这个问题的目的,请假设某些 C++ 程序员有时有充分的理由使用原始指针并希望它们自动为空而不求助于类内或构造函数初始化。

标签: c++ pointers c++11 unique-ptr


【解决方案1】:

C++11 允许在类中初始化数据成员。

class foo
{
  // ...
  lar *pr = nullptr;
};

除非您在构造函数中分配另一个值,否则将始终将 pr 初始化为 nullptr

【讨论】:

  • 是的。在我的特定情况下它没有帮助,但它是一个有效的答案。使用 lar* pr{} 参见上面 chris 的评论;——甚至更整洁。
  • @OldPeculier 为什么它对您的情况没有帮助?
  • 嗯,它解决了 DRY 问题,你只需要在声明时说一次nullptr,而不是在每个构造函数中重复它。它只是不能解决你根本不需要说的问题。
  • @OldPeculier 我必须相信你的话。不管这些原始指针的用途是什么,如果它们真的被声明为指针类型的数据成员,我不明白为什么很难将它们初始化为某个值。
  • @OldPeculier,从问题中删除 C++11 标签,我会 100% 同意你的看法。所有兼容的 C++11 工具链都适用于此答案。
【解决方案2】:

看起来您需要“世界上最笨的智能指针”,它被提议作为未来 C++ 标准模板库的补充。您可以在此处查看提案:"A Proposal for the World’s Dumbest Smart Pointer" 和此处:"A Proposal for the World’s Dumbest Smart Pointer, v2" 和此处:"A Proposal for the World’s Dumbest Smart Pointer, v3"

该提案包含一个潜在的部分实现,您可以对其进行调整以与您当前使用的编译器一起使用。该实现类似于 Mark Ransom 提供的 most_raw_ptr 解决方案。网络搜索 exclude_ptr 将提供更多详细信息。

关于世界上最笨的智能指针的提案,v3 已“将 exclude_ptr 重命名为observer_ptr”,请参阅链接文档了解其他更改。

【讨论】:

  • 甚至比 Mark Ransom 的出色答案更好,这准确地表达了我强调的需求,讨论了各种聪明的潜在解决方案,并概述了一个强有力的解决方案。谢谢。
  • 我不认为需要哑指针,我们可以简单地使用std::reference_wrapper,这基本上是一样的。
  • exempt_ptr 提案支持存储nullptr,并有一个默认构造函数,可以满足std::reference_wrapper 无法满足的用例。
【解决方案3】:
template<typename T>
class almost_raw_ptr
{
public:
    almost_raw_ptr(T* p = nullptr) : m_p(p) {}
    T* operator=(T* p) { m_p = p; return p; }
    operator T*() const { return m_p; }
    T* operator->() const { return m_p; }
    T& operator*() const { return *m_p; }
    T& operator[](int i) const { return m_p[i]; }

private:
    T* m_p;
};

如果您需要指针或对实际原始指针的引用,这将不起作用,但它应该适用于其他一切。

【讨论】:

  • 不。如果我们要走这条路,就需要比较运算符、复制构造函数和许多其他杂乱无章的东西。
  • 是的,我想如果您也想对其中一件事情进行指针运算,事情会变得更加令人兴奋。
  • 在许多情况下都需要operator*...(*ptr).foo()
  • @OldPeculier,与原始指针的自动转换将满足比较和复制的大部分需求。
  • @jmucchiello,开销不会比任何其他智能指针类型差。不能保证内联扩展,但标准库模板依赖于它以获得可接受的性能,这个模板也不例外。查看std::vector 的定义,并告诉我您对operator[] 的发现。
【解决方案4】:

当您使用原始指针并期望值0 表示指针尚未初始化时,值0 在某种意义上是“哨兵值”或“魔术cookie”。将魔术 cookie 视为指示状态的特殊值,但在其他方面与该数据类型的正常有效值无法区分。

由于所有常见原因,Magic cookie 很糟糕,这可能会触发您的感觉,即它很脆弱并且最终不需要。不使用哨兵值的解决方案可能会更好。

一个这样的解决方案是使用boost::optional

typedef Foo* FooPtr;
boost::optional <FooPtr> foo;
// ...
if (!foo)
{
  // the pointer is not yet initialized
} 
else
{
  // the pointer IS initialized
  Foo* theFoo = *foo;
}

初始化指针很简单:

foo = new Foo;

optional 不是标记值,因为您没有存储特殊值 Foo* 来指示指针未初始化。 optional 所做的唯一工作就是回答“指针是否已初始化?”的问题时说“是”或“否”

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-03-25
    • 1970-01-01
    • 2019-11-07
    • 1970-01-01
    • 2016-12-11
    • 2011-04-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多