如果我创建了一个 MyClass 类并且它有一些私有成员说 MyOtherClass,那么将 MyOtherClass 设为指针是否更好?就它在内存中的存储位置而言,将它作为不是指针意味着什么?创建类时会创建对象吗?
我注意到 QT 中的示例通常在类成员为类时将类成员声明为指针。
如果我创建了一个 MyClass 类并且它有一些私有成员说 MyOtherClass,那么将 MyOtherClass 设为指针是否更好?就它在内存中的存储位置而言,将它作为不是指针意味着什么?创建类时会创建对象吗?
我注意到 QT 中的示例通常在类成员为类时将类成员声明为指针。
如果我创建了一个 MyClass 类并且它有一些私有成员说 MyOtherClass,那么将 MyOtherClass 设为指针是否更好?
你通常应该在你的类中将它声明为一个值。它将是本地的,出错的机会更少,分配更少 - 最终可能出错的事情更少,并且编译器总是可以知道它在指定的偏移量处,所以......它有助于优化和二进制减少几个级别。在某些情况下,您知道必须处理指针(即多态、共享、需要重新分配),通常最好仅在必要时使用指针 - 特别是当它是私有/封装时。
就它在内存中的存储位置而言,将它作为不是指针意味着什么?
它的地址将接近(或等于)this
——gcc(例如)有一些高级选项来转储类数据(大小、vtables、偏移量)
创建类时会创建对象吗?
是 - MyClass 的大小将增加 sizeof(MyOtherClass),或者如果编译器重新对齐它(例如到它的自然对齐),则更多
看看这个例子:
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!
}
我们定义了两个类A
和B
. A
存储foo
类型的公共成员Foo
。B
有一个foo
类型的成员pointer to Foo
。
什么情况A
:
a_stack
类型变量,则该对象(显然)及其成员也在堆栈上。A
A
like的指针a_heap
,则只有指针变量在堆栈上;其他所有内容(对象及其成员)都在堆上。在以下情况下情况如何B
:
B
在堆栈上创建:然后对象及其成员foo
都在堆栈上,但foo
指向(指针)的对象在堆上。简而言之:(b_stack.foo
指针)在堆栈上,但*b_stack.foo
(指针)在堆上。B
named的指针b_heap
:(b_heap
指针)在堆栈上,*b_heap
(指针)在堆上,以及成员b_heap->foo
和*b_heap->foo
.foo
将通过调用隐式默认构造函数来自动创建Foo
. 这将创建一个integer
但不会初始化它(它将有一个随机数)!foo
(指针)也将被创建并用一个随机数初始化,这意味着它将指向堆上的一个随机位置。但请注意,指针存在!另请注意,隐式默认构造函数不会foo
为您分配任何东西,您必须显式执行此操作。这就是为什么您通常需要显式构造函数和伴随的析构函数来分配和删除成员指针的指针。不要忘记复制语义:如果您复制对象(通过复制构造或赋值),指针会发生什么?使用指向成员的指针有几个用例:
如果您的成员是指针并且您拥有它们,请格外小心。您必须编写适当的构造函数、析构函数并考虑复制构造函数和赋值运算符。如果复制对象,指针会发生什么?通常您还必须复制构造指针!
在 C++ 中,指针本身就是对象。它们并没有真正与它们指向的任何东西相关联,并且指针和它的指针之间没有特殊的交互(这是一个词吗?)
如果你创建一个指针,你创建一个指针,没有别的。您不会创建它可能指向或可能不指向的对象。当指针超出范围时,指向的对象不受影响。指针不会以任何方式影响它指向的任何东西的生命周期。
所以一般来说,你不应该默认使用指针。如果您的类包含另一个对象,则该其他对象不应该是指针。
但是,如果您的类知道另一个对象,那么指针可能是表示它的好方法(因为您的类的多个实例可以指向同一个实例,而无需获得它的所有权,也无需控制其生命周期)
C++ 中的常识是尽可能避免使用(裸)指针。尤其是指向动态分配内存的裸指针。
原因是指针使编写健壮的类变得更加困难,尤其是当您还必须考虑抛出异常的可能性时。
我遵循以下规则:如果成员对象与封装对象一起生存和死亡,则不要使用指针。如果成员对象由于某种原因必须比封装对象寿命长,您将需要一个指针。取决于手头的任务。
如果成员对象是给你的而不是你创建的,通常你会使用指针。然后你通常也不必破坏它。
这个问题可以无休止地考虑,但基础是:
如果 MyOtherClass 不是指针:
如果 MyOtherClass 是一个指针:
NULL
,这可能在您的上下文中有意义并且可以节省内存指针成员的一些优点:
将成员作为对象的优点:
如果将 MyOtherClass 对象设为 MyClass 的成员:
size of MyClass = size of MyClass + size of MyOtherClass
如果将 MyOtherClass 对象设为 MyClass 的指针成员:
size of MyClass = size of MyClass + size of any pointer on your system
您可能希望将 MyOtherClass 保留为指针成员,因为它使您可以灵活地将其指向从它派生的任何其他类。基本上可以帮助您实现动态多态性。
这取决于... :-)
如果您使用指针说 a class A
,则必须创建 A 类型的对象,例如在类的构造函数中
m_pA = new A();
此外,不要忘记在析构函数中销毁对象,否则会出现内存泄漏:
delete m_pA;
m_pA = NULL;
相反,在你的类中聚合一个类型为 A 的对象更容易,你不能忘记销毁它,因为这是在你的对象生命周期结束时自动完成的。
另一方面,拥有指针具有以下优点:
如果您的对象是在堆栈上分配的,并且类型 A 使用大量内存,则不会从堆栈分配,而是从堆分配。
您可以稍后构造您的 A 对象(例如在方法中Create
)或提前销毁它(在方法中Close
)
父类将与成员对象的关系作为指向成员对象的 (std::auto_ptr) 指针来维护的一个优点是,您可以转发声明对象,而不必包含对象的头文件。
这在构建时解耦了类,允许修改成员对象的头类,而不会导致父类的所有客户端也被重新编译,即使它们可能不访问成员对象的函数。
当你使用 auto_ptr 时,你只需要处理构造,你通常可以在初始化列表中做。auto_ptr 保证与父对象一起销毁。
要做的简单的事情是将您的成员声明为对象。这样,您就不必关心复制构造、销毁和分配。这一切都是自动处理的。
但是,在某些情况下,您仍然需要指针。毕竟,托管语言(如 C# 或 Java)实际上是通过指针保存成员对象。
最明显的情况是要保留的对象是多态的。正如您所指出的,在 Qt 中,大多数对象都属于多态类的巨大层次结构,并且通过指针保存它们是强制性的,因为您事先不知道成员对象的大小。
请注意这种情况下的一些常见陷阱,尤其是在处理泛型类时。异常安全是一个大问题:
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_;
};
如您所见,在存在指针的情况下拥有异常安全的构造函数非常麻烦。您应该查看 RAII 和智能指针(这里和网络上的其他地方有很多资源)。