我的班级CContainer
有一些成员CMemberX
,CMemberY
,这些成员彼此独立,并且使用其他CClientA
,CClientB
个类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
和CMember
修改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
类的实例在任何情况下都不会被复制或分配给对方。
答案 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都会产生相反的效果。与成员添加到代码的复杂性相比,在更改成员时再编译一个类的额外时间相对较小。
另外你在其他人的评论中提到代码并不像上面描述的那么干净。这只是强调了我的观点,即这是一个糟糕的优化,这将使得很难让类正常工作并使其保持在该状态。