对于数据成员,如果包含对象已经在动态内存中,则动态分配(或不分配)此变量之间有什么区别吗?
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 allocation
和 Class 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
的方式引入间接寻址的一些常见原因是:
- 抽象和隐藏。如果你试图抽象或隐藏一些 type/subtype、
Foo
的具体定义,那么这就是你需要间接访问的地方,以便它的句柄可以被捕获(或者甚至可能与抽象一起使用)那些不知道 Foo
是什么的人。
- 允许一个大的、连续的 1 块类型的对象在所有者之间传递,而无需调用副本或使其或其内容无效 references/pointers(包括迭代器)。
- 一种草率的理由虽然很浪费,但有时在截止日期匆忙中很有用,就是简单地将
validity/null
状态引入到本来不具备状态的事物中。
- 有时,它作为一种优化很有用,可以提升某个对象的某些不常访问的较大成员,以便其经常访问的元素更贴合(并且可能与相邻对象)在缓存行中。
unique_ptr
可以让您拆分该对象的内存布局,同时仍符合 RAII。
现在,如果您拥有一个实际上可以(明智地)由多个所有者拥有的容器,那么在标准容器顶部包装一个 shared_ptr
可能会有更多的合法应用程序。使用 unique_ptr
,一次只有一个所有者可以拥有该对象,标准容器已经允许您交换和移动彼此的内脏(大的、动态的部分)。所以我几乎没有理由想到用 unique_ptr
直接包装标准容器,因为它已经有点像指向动态数组的智能指针(但具有更多功能来处理动态数据,包括深度复制如果需要的话)。
如果我们谈论非标准容器,比如你正在使用第三方库提供一些数据结构,这些数据结构的内容可能会变得非常大,但它们无法提供那些廉价的、非无效的 move/swap
语义,那么你可以表面上将它包装在 unique_ptr
周围,交换一些 creation/access/destruction 开销以获得那些便宜的 move/swap
语义作为一种变通方法。对于标准容器,不需要这样的解决方法。
我首先假设,一般来说,在堆栈中分配小对象,在动态内存中分配大对象是个好主意。另一个假设是我在尝试了解内存、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 allocation 和 Class 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
的方式引入间接寻址的一些常见原因是:
- 抽象和隐藏。如果你试图抽象或隐藏一些 type/subtype、
Foo
的具体定义,那么这就是你需要间接访问的地方,以便它的句柄可以被捕获(或者甚至可能与抽象一起使用)那些不知道Foo
是什么的人。 - 允许一个大的、连续的 1 块类型的对象在所有者之间传递,而无需调用副本或使其或其内容无效 references/pointers(包括迭代器)。
- 一种草率的理由虽然很浪费,但有时在截止日期匆忙中很有用,就是简单地将
validity/null
状态引入到本来不具备状态的事物中。 - 有时,它作为一种优化很有用,可以提升某个对象的某些不常访问的较大成员,以便其经常访问的元素更贴合(并且可能与相邻对象)在缓存行中。
unique_ptr
可以让您拆分该对象的内存布局,同时仍符合 RAII。
现在,如果您拥有一个实际上可以(明智地)由多个所有者拥有的容器,那么在标准容器顶部包装一个 shared_ptr
可能会有更多的合法应用程序。使用 unique_ptr
,一次只有一个所有者可以拥有该对象,标准容器已经允许您交换和移动彼此的内脏(大的、动态的部分)。所以我几乎没有理由想到用 unique_ptr
直接包装标准容器,因为它已经有点像指向动态数组的智能指针(但具有更多功能来处理动态数据,包括深度复制如果需要的话)。
如果我们谈论非标准容器,比如你正在使用第三方库提供一些数据结构,这些数据结构的内容可能会变得非常大,但它们无法提供那些廉价的、非无效的 move/swap
语义,那么你可以表面上将它包装在 unique_ptr
周围,交换一些 creation/access/destruction 开销以获得那些便宜的 move/swap
语义作为一种变通方法。对于标准容器,不需要这样的解决方法。