动态分配成员的指针或引用始终存在?

时间:2010-01-04 12:50:01

标签: c++ pointers reference member

我的班级CContainer有一些成员CMemberXCMemberY,这些成员彼此独立,并且使用其他CClientACClientB个类CContainer

#include "MemberX.h"
#include "MemberY.h"

class CContainer
{
public:
    CMemberX & GetX() const { return m_x; }
    CMemberY & GetY() const { return m_y; }

private:
    CMemberX m_x;
    CMemberY m_y;
};

我想避免在使用前向声明和动态分配CClientCMember修改m_x个类之一时重新编译所有m_y类。

最初,我发了成员指点:

// Container.h
class CMemberX;
class CMemberY;

class CContainer
{
public:
    CContainer();
    ~CContainer();

    CMemberX & GetX() const { ASSERT(m_pX != NULL); return *m_pX; }
    CMemberY & GetY() const { ASSERT(m_pY != NULL); return *m_pY; }

private:
    CMemberX* m_pX;
    CMemberY* m_pY;
};

// Container.cpp
#include "Container.h"
#include "MemberX.h"
#include "MemberY.h"

// Allocate members on heap
CContainer::CContainer() : m_pX(new CMemberX()), m_pY(new CMemberY()) {}
CContainer::~CContainer() { delete m_pX; delete m_pY; }

然后我想,我也可以使用引用而不是指针,所以它看起来更像原始代码:

// Container.h
class CMemberX;
class CMemberY;

class CContainer
{
public:
    CContainer();
    ~CContainer();

    CMemberX & GetX() const { return m_x; }
    CMemberY & GetY() const { return m_y; }

private:
    CMemberX & m_x;
    CMemberY & m_y;
};

// Container.cpp
#include "Container.h"
#include "MemberX.h"
#include "MemberY.h"

// Allocate members on heap
CContainer::CContainer() : m_x(*new CMemberX()), m_y(*new CMemberY()) {}
CContainer::~CContainer() { delete &m_x; delete &m_y; }

我不喜欢指针成员的是看起来,就像指针可能是NULL一样,或者在运行时替换对象,但事实并非如此。

我不喜欢这些参考资料,CTor和DTor中的代码看起来有点hacky。

哪种方法更可取?有更好的解决方案吗?

关于复制/分配的注意事项: CContainer类的实例在任何情况下都不会被复制或分配给对方。

7 个答案:

答案 0 :(得分:5)

我认为这是const变量的用途:

CMember * const m_x;

初始化后无法更改m_x ...

答案 1 :(得分:4)

我认为在拥有所有权语义时使用引用有点令人惊讶。不是必然使一个坏主意,所有事情都考虑在内,但它确实会受到影响。

我认为在以下情况下我只使用过引用作为成员:

  • 向构造函数提供了一个对象,该对象需要比这个对象更长。
  • 无论如何都禁止转让。

因此,例如,注入的依赖项(如工厂或服务对象)可能是合适的。与此相反,在C ++中,您通常更喜欢使用模板参数而不是对象注入依赖项,因此可能不会出现问题。

我还发现,使用C ++的时间越长,我就越希望类型可以分配,除非有一个非常好的理由不这样做。由于某种原因,以你想要的方式减少编译时依赖性的常用技巧是“Pimpl”,而不是“Rimpl”。通过从对象成员切换到引用成员,您可以使您的类非默认可复制,以前可能是可复制的。此实现细节不应限制类的接口。使用Pimpl,您可以干净地实现分配和交换。使用这些引用,您必须分配或交换两个成员。如果第二次交换失败,您就失去了强大的异常保证:尽管如果您的CMemberX和CMemberY类具有无故障分配和交换,这无关紧要。

所以我认为我不喜欢这种情况下的引用,但后来我还没有看到你的其余代码。也许有一些原因可以解释为什么没有关于赋值的问题 - 例如,如果CContainer本身就是一个RAII类,那么通常它应该支持的唯一生命周期操作就是构造和破坏。

答案 2 :(得分:2)

这里有很多关于使用引用作为成员的可取性的问题(例如Should I prefer pointers or references in member data?),在我看来,多数意见(也恰好是我的意见)是 - 不要吨。如果您不希望更改指针使它们成为常量 - 我无法看到,鉴于您的代码,它们可能是NULL。

答案 3 :(得分:1)

动态分配对您的需求没有任何帮助:无需重新编译CClient和CContainer。

使用前向声明时唯一允许的用法是将指针声明为前向声明的类型。

只要您使用方法或前向声明类型的成员,它就不会编译:编译器必须知道它正在使用的完整类型。

简而言之:要么你永远不必重新编译[你只是声明指向前向声明类型的指针]或者你总是需要重新编译,以防你实际上使用CContainer。

答案 4 :(得分:1)

Steve Jessop已经提到了pImpl惯用语,但我认为如果你还没有看到它,你应该检查一下:Compilation Firewalls

答案 5 :(得分:0)

在您问题的第二个代码块中,您有私有成员指针,它们与父类一起初始化和销毁​​。这些信息应该足以让代码的读者了解正在发生的事情。

此外,您可以声明指针const:CMember* const m_pX;以指示在初始化后无法更改它们。现在,编译器将捕获意外更改。

答案 6 :(得分:0)

你真的不是在为自己买东西 (在有限的情况下编译时间略短)。

但是你正在堆积大量需要维护的其他代码。

如果这些对象是自然成员,则将其留作成员 通过在堆栈上创建它们并将它们存储为指针或引用,您必须提出一大堆需要代码才能回答它们的棘手问题。

  • 复制构造对象时会发生什么。
    • 我通常希望复制该对象及其所有成员 使用指针或引用,您将不得不做额外的工作来复制已经提供的此功能。
  • 分配对象时会发生什么。
    • 参考不起作用(尽管你通过使用提升参考来解决这个问题) 但是你仍然有期望复制成员的问题。
  • 删除对象时会发生什么。
    • 如果您已正确实施上述所有内容以使复印件正常 否则,您需要开始考虑共享指针以实现所需的功能。

因为版本2和版本3(问题中的代码)存在严重缺陷,唯一真正有效的版本是1.

在我看来,简单的事实是版本1维护成本会低得多,推荐版本2或版本3都会产生相反的效果。与成员添加到代码的复杂性相比,在更改成员时再编译一个类的额外时间相对较小。

另外你在其他人的评论中提到代码并不像上面描述的那么干净。这只是强调了我的观点,即这是一个糟糕的优化,这将使得很难让类正常工作并使其保持在该状态。