2

我有一个类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;
};

我想避免在使用前向声明和动态分配和CClient修改其中一个类时重新编译所有类。CMemberm_xm_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 中的代码看起来有点老套。

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

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

4

7 回答 7

5

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

CMember * const m_x;

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

于 2010-01-04T12:55:06.173 回答
4

我认为当存在所有权语义时使用引用有点令人惊讶。考虑到所有因素,这并不一定是一个坏主意,但它确实会产生不利影响。

我想我只在以下两种情况下才使用引用作为成员:

  • 向构造函数提供了一个对象,该对象比该对象寿命更长。
  • 无论如何,分配是禁止的。

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

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

所以我认为我不喜欢这种情况下的参考,但是我还没有看到你的其余代码。也许有一些原因为什么没有关于分配的担忧 - 例如,如果 CContainer 本身是一个 RAII 类,那么通常它应该支持的唯一生命周期操作是构造和销毁。

于 2010-01-04T13:51:39.400 回答
2

这里有很多关于使用引用作为成员的可取性的问题(例如,我应该更喜欢成员数据中的指针还是引用?),在我看来,大多数意见(也恰好是我的)是 -不。如果您不想更改指针,请将它们设为 const - 根据您的代码,我看不出它们如何可能为 NULL。

于 2010-01-04T12:54:02.177 回答
1

就你想要的而言,动态分配对你没有任何帮助:不必重新编译 CClient 和 CContainer。

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

一旦你使用一个方法或前向声明类型的成员,它就不会编译:编译器必须知道它正在使用的任何东西的完整类型。

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

于 2010-01-04T12:54:25.563 回答
1

Steve Jessop 已经顺便提到了 pImpl 成语,但我认为如果你还没有遇到过它,你应该检查一下:编译防火墙

于 2010-01-04T17:27:14.840 回答
0

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

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

于 2010-01-04T13:17:16.603 回答
0

你并没有真正给自己买任何东西。
(在有限的情况下编译时间略短)。

但是您将大量需要维护的其他代码堆积在您的盘子上。

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

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

就目前而言,版本 2 和 3(问题中的代码)存在严重缺陷,唯一有效的版本是 1。

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

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

于 2010-01-04T14:28:17.703 回答