对于数据成员,如果包含对象已经在动态内存中,则动态分配(或不分配)此变量之间有什么区别吗?

For a data member, is there any difference between dynamically allocating this variable(or not) if the containing object is already in dynamic memory?

我首先假设,一般来说,在堆栈中分配小对象,在动态内存中分配大对象是个好主意。另一个假设是我在尝试了解内存、STL 容器和智能指针时可能会感到困惑。

考虑以下示例,我有一个对象必须通过智能指针在自由存储中分配,例如,我可以依赖客户端从工厂获取该对象。这个对象包含一些使用STL容器专门分配的数据,恰好是一个std::vector。在一种情况下,这个数据向量本身是使​​用一些智能指针动态分配的,而在另一种情况下,我只是不使用智能指针。

设计 A 和设计 B 之间是否存在任何实际差异,如下所述?

情况A:

class SomeClass{
public:
    SomeClass(){ /* initialize some potentially big STL container */ }
private:
    std::vector<double> dataVector_;
};

情况B:

class SomeOtherClass{
public:
    SomeOtherClass() { /* initialize some potentially big STL container,
                        but is it allocated in any different way? */ }
private:
    std::unique_ptr<std::vector<double>> pDataVector_;
};

一些工厂函数。

std::unique_ptr<SomeClass> someClassFactory(){
    return std::make_unique<SomeClass>();
}

std::unique_ptr<SomeOtherClass> someOtherClassFactory(){
    return std::make_unique<SomeOtherClass>();
}

用例:

int main(){
    //in my case I can reliably assume that objects themselves
    //are going to always be allocated in dynamic memory
    auto pSomeClassObject(someClassFactory());
    auto pSomeOtherClassObject(someOtherClassFactory());

    return 0;
}

我希望这两种设计选择具有相同的结果,但它们是吗? 选择 A 或 B 有什么优势或劣势?具体来说,我一般应该选择设计A,因为它更简单还是有更多的考虑? B 在道德上是错误的,因为它可以悬挂 std::vector?

tl;dr : 智能指针指向STL容器是不是错了?

编辑: 相关答案为像我一样困惑的人指出了有用的附加信息。 Usage of objects or pointers to objects as class members and memory allocationClass members that are objects - Pointers or not? C++ 并更改一些 google 关键字将我带到 When vectors are allocated, do they use memory on the heap or the stack?

std::unique_ptr<std::vector<double>>速度较慢,占用内存较多,唯一的优点是它包含了一个额外的可能状态:"vector doesn't exist"。但是,如果您关心该状态,请改用 boost::optional<std::vector>。您几乎不应该拥有堆分配的容器,并且绝对不应该使用 unique_ptr。它实际上工作正常,没有 "dangling",它只是毫无意义地慢。

我同意@MooingDuck;我认为使用 std::unique_ptr 没有任何引人注目的优势。但是,如果成员数据非常大并且 class 将支持 COW(写时复制)语义(或数据所在的任何其他用例),我可以看到 std::shared_ptr 的用例跨多个实例共享)。

在这里使用 std::unique_ptr 只是浪费,除非您的目标是编译器防火墙(基本上隐藏对 vector 的编译时依赖性,但是您需要对标准容器进行前向声明)。

您正在添加一个间接寻址,但更重要的是,SomeClass 的全部内容在访问内容时变成了 3 个单独的内存块来加载(SomeClass 合并 with/containing unique_ptr's 块指向 std::vector's 块指向它的元素数组)。此外,您还额外支付了一层多余的堆开销。

现在您可能会开始想象间接对 vector 有帮助的场景,例如您可以浅化 move/swap 两个 SomeClass 实例之间的 unique_ptrs。是的,但是 vector 已经提供了顶部没有 unique_ptr 包装器的情况。它已经有像 empty 这样的状态,你可以重复使用 validity/nilness.

的一些概念

请记住,可变大小的容器本身是小对象,而不是大对象,指向潜在的大块。 vector不大,动态内容还可以。为大对象添加间接寻址的想法并不是一个糟糕的经验法则,但是 vector 并不是一个大对象。有了移动语义,值得将它更像是一个指向大内存块的小内存块,可以廉价地进行浅层复制和交换。在移动语义之前,有更多理由将 std::vector 之类的东西视为一个不可分割的大对象(尽管其内容总是可交换的),但现在更值得将其视为指向大动态内容的小句柄.

通过类似 unique_ptr 的方式引入间接寻址的一些常见原因是:

  1. 抽象和隐藏。如果你试图抽象或隐藏一些 type/subtype、Foo 的具体定义,那么这就是你需要间接访问的地方,以便它的句柄可以被捕获(或者甚至可能与抽象一起使用)那些不知道 Foo 是什么的人。
  2. 允许一个大的、连续的 1 块类型的对象在所有者之间传递,而无需调用副本或使其或其内容无效 references/pointers(包括迭代器)。
  3. 一种草率的理由虽然很浪费,但有时在截止日期匆忙中很有用,就是简单地将 validity/null 状态引入到本来不具备状态的事物中。
  4. 有时,它作为一种优化很有用,可以提升某个对象的某些不常访问的较大成员,以便其经常访问的元素更贴合(并且可能与相邻对象)在缓存行中。 unique_ptr 可以让您拆分该对象的内存布局,同时仍符合 RAII。

现在,如果您拥有一个实际上可以(明智地)由多个所有者拥有的容器,那么在标准容器顶部包装一个 shared_ptr 可能会有更多的合法应用程序。使用 unique_ptr,一次只有一个所有者可以拥有该对象,标准容器已经允许您交换和移动彼此的内脏(大的、动态的部分)。所以我几乎没有理由想到用 unique_ptr 直接包装标准容器,因为它已经有点像指向动态数组的智能指针(但具有更多功能来处理动态数据,包括深度复制如果需要的话)。

如果我们谈论非标准容器,比如你正在使用第三方库提供一些数据结构,这些数据结构的内容可能会变得非常大,但它们无法提供那些廉价的、非无效的 move/swap 语义,那么你可以表面上将它包装在 unique_ptr 周围,交换一些 creation/access/destruction 开销以获得那些便宜的 move/swap 语义作为一种变通方法。对于标准容器,不需要这样的解决方法。