[英]Class members that are objects - Pointers or not? C++
If I create a class MyClass and it has some private member say MyOtherClass, is it better to make MyOtherClass a pointer or not?如果我创建一个类 MyClass 并且它有一些私有成员说 MyOtherClass,那么将 MyOtherClass 设为指针是否更好? What does it mean also to have it as not a pointer in terms of where it is stored in memory?
就它在内存中的存储位置而言,让它不是指针又意味着什么? Will the object be created when the class is created?
创建类时会创建对象吗?
I noticed that the examples in QT usually declare class members as pointers when they are classes.我注意到 QT 中的示例通常在类成员是类时将类成员声明为指针。
If I create a class MyClass and it has some private member say MyOtherClass, is it better to make MyOtherClass a pointer or not?
如果我创建一个类 MyClass 并且它有一些私有成员说 MyOtherClass,那么将 MyOtherClass 设为指针是否更好?
you should generally declare it as a value in your class.你通常应该在你的类中将它声明为一个值。 it will be local, there will be less chance for errors, fewer allocations -- ultimately fewer things that could go wrong, and the compiler can always know it is there at a specified offset so... it helps optimization and binary reduction at a few levels.
它将是本地的,出错的机会更少,分配更少 - 最终可能出错的事情更少,并且编译器总是可以知道它在指定的偏移量处,所以......它有助于优化和二进制减少几个级别。 there will be a few cases where you know you'll have to deal with pointer (ie polymorphic, shared, requires reallocation), it is typically best to use a pointer only when necessary - especially when it is private/encapsulated.
在某些情况下,您知道必须处理指针(即多态、共享、需要重新分配),通常最好仅在必要时使用指针 - 特别是当它是私有/封装时。
What does it mean also to have it as not a pointer in terms of where it is stored in memory?
就它在内存中的存储位置而言,让它不是指针又意味着什么?
its address will be close to (or equal to) this
-- gcc (for example) has some advanced options to dump class data (sizes, vtables, offsets)它的地址将接近(或等于)
this
——gcc(例如)有一些高级选项来转储类数据(大小、虚拟表、偏移量)
Will the object be created when the class is created?
创建类时会创建对象吗?
yes - the size of MyClass will grow by sizeof(MyOtherClass), or more if the compiler realigns it (eg to its natural alignment)是 - MyClass 的大小将增加 sizeof(MyOtherClass),或者更多,如果编译器重新对齐它(例如到它的自然对齐)
Take a look at this example:看看这个例子:
struct Foo { int m; };
struct A {
Foo foo;
};
struct B {
Foo *foo;
B() : foo(new Foo()) { } // ctor: allocate Foo on heap
~B() { delete foo; } // dtor: Don't forget this!
};
void bar() {
A a_stack; // a_stack is on stack
// a_stack.foo is on stack too
A* a_heap = new A(); // a_heap is on stack (it's a pointer)
// *a_heap (the pointee) is on heap
// a_heap->foo is on heap
B b_stack; // b_stack is on stack
// b_stack.foo is on stack
// *b_stack.foo is on heap
B* b_heap = new B(); // b_heap is on stack
// *b_heap is on heap
// b_heap->foo is on heap
// *(b_heap->foo is on heap
delete a_heap;
delete b_heap;
// B::~B() will delete b_heap->foo!
}
We define two classes A
and B
.我们定义了两个类
A
和B
。 A
stores a public member foo
of type Foo
. A
存储Foo
类型的公共成员foo
。 B
has a member foo
of type pointer to Foo
. B
有一个pointer to Foo
类型的成员foo
。
What's the situation for A
: A
的情况是什么:
a_stack
of type A
on the stack , then the object (obviously) and its members are on the stack too.A
的变量a_stack
,则该对象(显然)及其成员也在堆栈上。A
like a_heap
in the above example, just the pointer variable is on the stack ;a_heap
一样创建一个指向A
的指针,那么只有指针变量在堆栈上; everything else (the object and it's members) are on the heap . What does the situation look like in case of B
:在
B
情况下情况如何:
B
on the stack : then both the object and its member foo
are on the stack , but the object that foo
points to (the pointee) is on the heap .B
:然后对象及其成员foo
都在堆栈上,但foo
指向的对象(指针对象)在堆上。 In short: b_stack.foo
(the pointer) is on the stack, but *b_stack.foo
the (pointee) is on the heap.b_stack.foo
(指针)在栈上,但*b_stack.foo
(指针)在堆上。B
named b_heap
: b_heap
(the pointer) is on the stack, *b_heap
(the pointee) is on the heap , as well as the member b_heap->foo
and *b_heap->foo
.b_heap
的指向B
的指针: b_heap
(指针)在堆栈上, *b_heap
(指针)在heap 上,以及成员b_heap->foo
和*b_heap->foo
。foo
will automatically be created by calling the implicit default constructor of Foo
.foo
将通过调用Foo
的隐式默认构造函数自动创建。 This will create an integer
but will not intitialize it (it will have a random number)!integer
但不会初始化它(它将有一个随机数)!foo
(the pointer) will also be created and initialized with a random number which means that it will point to a random location on the heap.foo
(指针)也将被创建并用一个随机数初始化,这意味着它将指向堆上的一个随机位置。 But note, that the pointer exists!foo
for you, you have to do this explicitly .foo
的内容,您必须显式执行此操作。 That's why you usually need an explicit constructor and a accompanying destructor to allocate and delete the pointee of your member pointer. There are several use cases of using a pointer to a member:使用指向成员的指针有几种用例:
Take extra care if your members are pointers and you own them.如果您的成员是指针并且您拥有它们,请格外小心。 You have to write proper constructors, destructors and think about copy constructors and assignment operators.
您必须编写适当的构造函数、析构函数并考虑复制构造函数和赋值运算符。 What happens to the pointee if you copy the object?
如果复制对象,指针对象会发生什么? Usually you will have to copy construct the pointee as well!
通常,您还必须复制构造指针!
In C++, pointers are objects in their own right.在 C++ 中,指针本身就是对象。 They're not really tied to whatever they point to, and there's no special interaction between a pointer and its pointee (is that a word?)
它们并没有真正绑定到它们指向的任何东西,并且指针和它的被指点对象之间没有特殊的交互(这是一个词吗?)
If you create a pointer, you create a pointer and nothing else .如果你创建一个指针,你就创建了一个指针而不是其他任何东西。 You don't create the object that it might or might not point to.
您不会创建它可能指向或不指向的对象。 And when a pointer goes out of scope, the pointed-to object is unaffected.
当指针超出范围时,指向的对象不受影响。 A pointer doesn't in any way affect the lifetime of whatever it points to.
指针不会以任何方式影响它指向的任何东西的生命周期。
So in general, you should not use pointers by default.因此,在一般情况下,你不应该使用默认的指针。 If your class contains another object, that other object shouldn't be a pointer.
如果您的类包含另一个对象,则该其他对象不应是指针。
However, if your class knows about another object, then a pointer might be a good way to represent it (since multiple instances of your class can then point to the same instance, without taking ownership of it, and without controlling its lifetime)但是,如果您的类知道另一个对象,那么指针可能是表示它的好方法(因为您的类的多个实例可以指向同一个实例,无需取得它的所有权,也无需控制其生命周期)
The common wisdom in C++ is to avoid the use of (bare) pointers as much as possible. C++ 中的常识是尽可能避免使用(裸)指针。 Especially bare pointers that point to dynamically allocated memory.
特别是指向动态分配内存的裸指针。
The reason is because pointers make it more difficult to write robust classes, especially when you also have to consider the possibility of exceptions being thrown.原因是因为指针使得编写健壮的类变得更加困难,尤其是当您还必须考虑抛出异常的可能性时。
I follow the following rule: if the member object lives and dies with the encapsulating object, do not use pointers.我遵循以下规则:如果成员对象与封装对象一起生存和死亡,则不要使用指针。 You will need a pointer if the member object has to outlive the encapsulating object for some reason.
如果成员对象由于某种原因必须比封装对象存活时间更长,则您将需要一个指针。 Depends on the task at hand.
取决于手头的任务。
Usually you use a pointer if the member object is given to you and not created by you.如果成员对象是给你的而不是你创建的,通常你会使用指针。 Then you usually don't have to destroy it either.
那么你通常也不必销毁它。
This question could be deliberated endlessly, but the basics are:这个问题可以无休止地讨论,但基础是:
If MyOtherClass is not a pointer:如果 MyOtherClass 不是指针:
If MyOtherClass is a pointer:如果 MyOtherClass 是一个指针:
NULL
, which could have meaning in your context and could save memory NULL
,这在您的上下文中可能有意义并且可以节省内存Some advantages of pointer member:指针成员的一些优点:
Advantages of having the member as an object:将成员作为对象的优点:
If you make the MyOtherClass object as member of your MyClass:如果您将 MyOtherClass 对象设为 MyClass 的成员:
size of MyClass = size of MyClass + size of MyOtherClass
If you make the MyOtherClass object as pointer member of your MyClass:如果将 MyOtherClass 对象设为 MyClass 的指针成员:
size of MyClass = size of MyClass + size of any pointer on your system
You might want to keep MyOtherClass as a pointer member because it gives you the flexibility to point it to any other class that is derived from it.您可能希望将 MyOtherClass 作为指针成员保留,因为它使您可以灵活地将其指向从它派生的任何其他类。 Basically helps you implement dynamice polymorphism.
基本上可以帮助您实现动态多态性。
It depends... :-)这取决于... :-)
If you use pointers to say a class A
, you have to create the object of type A eg in the constructor of your class如果您使用指针表示
class A
,则必须在类的构造函数中创建类型 A 的对象,例如
m_pA = new A();
Moreover, don't forget to destroy the object in the destructor or you have a memory leak:此外,不要忘记在析构函数中销毁对象,否则会出现内存泄漏:
delete m_pA;
m_pA = NULL;
Instead, having an object of type A aggregated in your class is easier, you can't forget to destroy it, because this is done automatically at the end of lifetime of your object.相反,在您的类中聚合类型为 A 的对象更容易,您不能忘记销毁它,因为这会在您的对象生命周期结束时自动完成。
On the other hand, having a pointer has the following advantages:另一方面,拥有指针具有以下优点:
If your object is allocated on the stack and type A uses a lot of memory this won't be allocated from the stack but from the heap.如果您的对象在堆栈上分配并且类型 A 使用大量内存,则不会从堆栈分配而是从堆分配。
You can construct your A object later (eg in a method Create
) or destroy it earlier (in method Close
)您可以稍后构造您的 A 对象(例如在方法
Create
)或提前销毁它(在方法Close
)
An advantage of the parent class maintaining the relation to a member object as a (std::auto_ptr) pointer to the member object is that you can forward declare the object rather than having to include the object's header file.父类将与成员对象的关系作为指向成员对象的 (std::auto_ptr) 指针来维护的一个优点是,您可以向前声明该对象,而不必包含该对象的头文件。
This decouples the classes at build time allowing to modify the member object's header class without causing all the clients of your parent class to be recompiled as well even though they probably do not access the member object's functions.这在构建时解耦了类,允许修改成员对象的头类,而不会导致父类的所有客户端也被重新编译,即使它们可能不访问成员对象的函数。
When you use an auto_ptr, you only need to take care of construction, which you could typically do in the initializer list.当您使用 auto_ptr 时,您只需要注意构造,这通常可以在初始化列表中完成。 Destruction along with the parent object is guaranteed by the auto_ptr.
auto_ptr 保证与父对象一起销毁。
The simple thing to do is to declare your members as objects.要做的简单的事情是将您的成员声明为对象。 This way, you do not have to care about copy construction, destruction and assignment.
这样,您就不必关心复制构造、销毁和赋值。 This is all taken care of automatically.
这一切都是自动处理的。
However, there are still some cases when you want pointers.但是,仍有一些情况需要指针。 After all, managed languages (like C# or Java) actually hold member objects by pointers.
毕竟,托管语言(如 C# 或 Java)实际上是通过指针来保存成员对象的。
The most obvious case is when the object to be kept is polymorphic.最明显的情况是要保留的对象是多态的。 In Qt, as you pointed out, most objects belong to a huge hierarchy of polymorphic classes, and holding them by pointers is mandatory since you don't know at advance what size will the member object have.
在 Qt 中,正如您所指出的,大多数对象都属于一个庞大的多态类层次结构,并且必须通过指针来保存它们,因为您事先不知道成员对象的大小。
Please beware of some common pitfalls in this case, especially when you deal with generic classes.在这种情况下,请注意一些常见的陷阱,尤其是在处理泛型类时。 Exception safety is a big concern:
异常安全是一个大问题:
struct Foo
{
Foo()
{
bar_ = new Bar();
baz_ = new Baz(); // If this line throw, bar_ is never reclaimed
// See copy constructor for a workaround
}
Foo(Foo const& x)
{
bar_ = x.bar_.clone();
try { baz_ = x.baz_.clone(); }
catch (...) { delete bar_; throw; }
}
// Copy and swap idiom is perfect for this.
// It yields exception safe operator= if the copy constructor
// is exception safe.
void swap(Foo& x) throw()
{ std::swap(bar_, x.bar_); std::swap(baz_, x.baz_); }
Foo& operator=(Foo x) { x.swap(*this); return *this; }
private:
Bar* bar_;
Baz* baz_;
};
As you see, it is quite cumbersome to have exception safe constructors in the presence of pointers.如您所见,在存在指针的情况下使用异常安全构造函数非常麻烦。 You should look at RAII and smart pointers (there are plenty of resources here and somewhere else on the web).
您应该查看 RAII 和智能指针(这里和网络上的其他地方有很多资源)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.