简体   繁体   English

作为对象的类成员 - 指针与否? C++

[英]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),或者更多,如果编译器重新对齐它(例如到它的自然对齐)

Where is your member stored in memory?您的成员存储在内存中的什么位置?

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 .我们定义了两个类AB 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的情况是什么:

  • If you create a variable a_stack of type A on the stack , then the object (obviously) and its members are on the stack too.如果您在stack上创建类型为A的变量a_stack ,则该对象(显然)及其成员也在堆栈上
  • If you create a pointer to 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情况下情况如何:

  • you create 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 (指针)在堆上。
  • you create a pointer to 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

Will the object be automagically created?对象会自动创建吗?

  • In case of A: Yes, foo will automatically be created by calling the implicit default constructor of Foo .在 A 的情况下:是的, foo将通过调用Foo的隐式默认构造函数自动创建。 This will create an integer but will not intitialize it (it will have a random number)!这将创建一个integer不会初始化它(它将有一个随机数)!
  • In case of B: If you omit our ctor and dtor then 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.在 B 的情况下:如果你省略了我们的 ctor 和 dtor,那么foo (指针)也将被创建并用一个随机数初始化,这意味着它将指向堆上的一个随机位置 But note, that the pointer exists!但请注意,指针存在! Note also, that the implicit default constructor won't allocate something for 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.这就是为什么您通常需要一个显式构造函数和一个伴随的析构函数来分配和删除成员指针的指针对象。 Don't forget about copy semantics : what happens to the pointee if your copy the object (via copy construction or assignment)?不要忘记复制语义:如果您复制对象(通过复制构造或赋值),指向对象会发生什么?

What's the point of all of this?这一切的意义何在?

There are several use cases of using a pointer to a member:使用指向成员的指针有几种用例:

  • To point to an object you don't own.指向您不拥有的对象。 Let's say your class needs access to a huge data structure that is very costly to copy.假设您的类需要访问一个复制成本非常高的庞大数据结构。 Then you could just save a pointer to this data structure.然后你可以保存一个指向这个数据结构的指针。 Be aware that in this case creation and deletion of the data structure is out of the scope of your class.请注意,在这种情况下,数据结构的创建删除超出了您的类的范围。 Someone other has to take care.其他人必须照顾。
  • Increasing compilation time, since in your header file the pointee does not have to be defined.增加编译时间,因为在您的头文件中不必定义指针对象。
  • A bit more advanced;更先进一点; When your class has a pointer to another class that stores all private members, the "Pimpl idiom": http://c2.com/cgi/wiki?PimplIdiom , take also a look at Sutter, H. (2000): Exceptional C++ , p.当你的类有一个指向另一个存储所有私有成员的类的指针时,“Pimpl idiom”: http ://c2.com/cgi/wiki?PimplIdiom ,也看看 Sutter, H. (2000): Exceptional C++ ,第99--119 99--119
  • And some others, look at the other answers还有一些,看看其他答案

Advice忠告

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 不是指针:

  • The creation and destruction of MyOtherClass is automatic, which can reduce bugs. MyOtherClass 的创建和销毁是自动的,可以减少 bug。
  • The memory used by MyOtherClass is local to the MyClassInstance, which could improve performance. MyOtherClass 使用的内存是 MyClassInstance 的本地内存,这可以提高性能。

If MyOtherClass is a pointer:如果 MyOtherClass 是一个指针:

  • The creation and destruction of MyOtherClass is your responsibility MyOtherClass 的创建和销毁是你的责任
  • MyOtherClass may be NULL , which could have meaning in your context and could save memory MyOtherClass 可能是NULL ,这在您的上下文中可能有意义并且可以节省内存
  • Two instances of MyClass could share the same MyOtherClass MyClass 的两个实例可以共享同一个 MyOtherClass

Some advantages of pointer member:指针成员的一些优点:

  • The child (MyOtherClass) object can have different lifetime than its parent (MyClass).子对象 (MyOtherClass) 可以具有与其父对象 (MyClass) 不同的生命周期。
  • The object can possibly be shared between several MyClass (or other) objects.该对象可能会在多个 MyClass(或其他)对象之间共享。
  • When compiling the header file for MyClass, the compiler doesn't necessarily have to know the definition of MyOtherClass.在为 MyClass 编译头文件时,编译器不一定要知道 MyOtherClass 的定义。 You don't have to include its header, thus decreasing compile times.您不必包含其标头,从而减少编译时间。
  • Makes MyClass size smaller.使 MyClass 大小更小。 This can be important for performance if your code does a lot of copying of MyClass objects.如果您的代码对 MyClass 对象进行了大量复制,这对于性能来说可能很重要。 You can just copy the MyOtherClass pointer and implement some kind of reference counting system.您可以只复制 MyOtherClass 指针并实现某种引用计数系统。

Advantages of having the member as an object:将成员作为对象的优点:

  • You don't have to explicitely write code to create and destroy the object.您不必明确地编写代码来创建和销毁对象。 It's easier and and less error-prone.它更容易,也更不容易出错。
  • Makes memory management more efficient because only one block of memory needs to be allocated instead of two.使内存管理更高效,因为只需要分配一块内存而不是两块。
  • Implementing assignment operators, copy/move constructors etc is much simpler.实现赋值运算符、复制/移动构造函数等要简单得多。
  • More intuitive更直观

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM