【问题标题】:Reason for using smart pointers with a container在容器中使用智能指针的原因
【发布时间】:2020-08-26 20:48:33
【问题描述】:

写得简单我想问“使用智能指针的好理由是什么?” 前std::unique_ptr

但是,我并不是在询问使用智能指针的原因而不是常规(哑)指针。我想每个人都知道,或者快速搜索可以找到原因。

我要问的是这两种情况的比较:

给定一个名为 MyObject 的类(或结构)使用

  1. std:queue<std::unique_ptr<MyObject>>queue;

而不是

  1. std:queue<MyObject> queue;

(可以是任何容器,不一定是队列)

为什么有人应该使用选项 1 而不是选项 2?

【问题讨论】:

  • 1.能够存储派生自 MyObject 的类。 2. 作为std::optional<MyObject> 的替代品。 3. 能够存储不可移动(和不可复制)的类。
  • 智能指针 (IMO) 的主要原因是所有权。谁“拥有”资源?该所有权是否可以共享 (std::shared_pointer) 或不共享 (std::unique_ptr)?
  • @Someprogrammerdude std:queue<MyObject> 也定义了所有权。
  • 表达这个问题的更简单的方法是:何时使用动态存储?

标签: c++ c++11 containers smart-pointers


【解决方案1】:

这其实是个好问题。

我能想到的原因有几个:

  • 多态性仅适用于引用和指针,不适用于值类型。因此,如果您想在容器中保存派生对象,则不能拥有std::queue<MyObject>。一个选项是unique_ptr,另一个是reference_wrapper

  • 包含的对象是从容器外部引用 (*) 的。根据容器的不同,它保存的元素可以移动,从而使之前对它的引用无效。例如std::vector::insert 或容器本身的移动。在这种情况下,std::unique_ptr<MyObject> 确保引用是有效的,无论容器如何处理它(ofc,只要 unique_ptr 还活着)。
    Objects 的以下示例中,您可以在队列中添加一堆对象。但是,其中两个对象可能是特殊的,您可以随时访问这两个对象。

    struct MyObject { MyObject(int); };
    
    struct Objects
    {
        std::queue<std::unique_ptr<MyObject>> all_objects_;
    
        MyObject* special_object_ = nullptr;
        MyObject* secondary_special_object_ = nullptr;
    
        void AddObject(int i)
        {
            all_objects_.emplace(std::make_unique<MyObject>(i));
        }
    
        void AddSpecialObject(int i)
        {
            auto& emplaced = all_objects_.emplace(std::make_unique<MyObject>(i));
            special_object_ = emplaced.get();
        }
    
        void AddSecondarySpecialObject(int i)
        {
            auto& emplaced = all_objects_.emplace(std::make_unique<MyObject>(i));
            secondary_special_object_ = emplaced.get();
        }
    };
    

(*) 我在这里使用“reference”的英文意思,而不是 C++ 类型。引用对象的任何方式(例如通过原始指针)

【讨论】:

  • 那么您的意思是当我想要保存派生对象时,或者当我在容器外部创建对象并将它们放入容器时?
  • 如果您在外部创建一个对象并将其放入容器中,则不需要unique_ptr。如果您持有指向容器中元素的原始指针,则需要 unique_ptr
  • @KansaiRobot 我在答案中添加了一个示例。
  • 更一般地说:MyObject 可能是不可移动和不可复制的(或者太贵了)。 std::dequestd::vector 需要这个(std::queue 等这些类型的适配器也是如此)。
【解决方案2】:

用例:您希望在 std::vector 中存储具有常量索引的内容,同时能够从该向量中删除对象。

如果你使用指针,你可以删除一个指向的对象并设置vector[i] = nullptr,(并且稍后检查它)这是你在存储对象本身时不能做的事情。如果您要存储对象,则必须将实例保留在向量中并使用标志bool valid 或其他东西,因为如果您要从向量中删除对象,则该对象的索引更改为-1之后的所有索引。


注意:如对此答案的评论中所述,如果您有权访问 C++17 或更高版本,则可以使用 std::optional 进行归档。

【讨论】:

  • 所描述的用例也可以使用std::optional 来实现,并且可选的好处是可以保证作为可选的一部分分配的值,并且具有不需要的好处每个要存储的对象都有自己的分配。
  • @t.niese 是的,你是对的。不过,我正在使用 boost 的池分配器来规避分配问题。
【解决方案3】:

第一个声明生成一个带有指针元素的容器,第二个声明生成纯对象。

以下是在对象上使用指针的一些好处:

  1. 它们允许您创建动态大小的数据结构。
  2. 它们允许您直接操作内存(例如在打包或 从硬件设备中解压数据。)
  3. 它们允许对象引用(函数或数据对象)
  4. 它们允许您(通过 API)操作对象,而无需了解对象的详细信息(API 除外)。
  5. (原始)指针通常与 CPU 寄存器很好地匹配,这使得通过指针解引用值变得高效。 (C++“智能”指针是更复杂的数据对象。)

此外,多态性被认为是面向对象编程的重要特征之一。 在C++中多态主要分为两种:

  • 编译时多态性

这种类型的多态是通过函数重载或运算符重载来实现的。

  • 运行时多态性

这种类型的多态是通过函数覆盖实现的,如果我们要使用基类来使用这些函数,就必须使用指针而不是对象。

【讨论】:

  • 问题是关于在容器中使用指针,而不是一般使用指针的可能优势。
  • 我不同意这里的大部分观点。 1 大部分容器已经动态分配内存,另外一层间接不是好事。 2. 你不能用智能指针来做到这一点。 3. 我不明白。 4. 值对象也是如此 5. 带有默认删除器的unique_ptr 是一个 0 成本抽象,它绝对不会增加原始指针的开销。
猜你喜欢
  • 2022-01-23
  • 1970-01-01
  • 1970-01-01
  • 2014-03-04
  • 2011-07-25
  • 2014-07-25
  • 2019-07-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多