始终存在的动态分配成员的指针或引用?
我有一个类 CContainer
,它有一些成员 CMemberX
、CMemberY
,它们彼此独立且与其他 CClientA
、使用 CContainer
的 CClientB
类。
#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;
};
我希望避免在使用前向声明以及 m_x
和 动态分配修改
。CMember
类之一时重新编译所有 CClient
类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; }
我不喜欢指针成员的是它看起来< /strong> 就像指针可以为 NULL 或对象在运行时被替换一样,但事实并非如此。
我不喜欢这些参考资料的是 CTor 和 DTor 中的代码看起来有点老套。
哪种方法更可取?有更好的解决方案吗?
有关复制/分配的注意事项:CContainer
类的实例在任何情况下都不会相互复制或分配。
I have a class CContainer
that has some members CMemberX
, CMemberY
, which are independent of each other and other CClientA
, CClientB
classes that use 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;
};
I want to avoid having to recompile all CClient
classes when modifying one of the CMember
classes using forward declarations and dynamic allocation of m_x
and m_y
.
Initially, I made the members pointers:
// 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; }
Then I thought, that I could as well use references instead of pointers, so it looks more like the original code:
// 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; }
What I don't like about the pointer members is that it looks like the pointers could be NULL
or the objects be replaced at runtime, which is not the case.
What I don't like about the references is that the code in the CTor and DTor looks a bit hacky.
Which approach is preferable? Is there a better solution?
Note regarding copying/assigning: Instances of the CContainer
class will not under any circumstances be copied or assigned to each other.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
我认为这就是 const 变量的用途:
初始化后无法更改 m_x...
I think that's what the const variables are for:
Cannot change m_x after initialization...
我认为当存在所有权语义时使用引用有点令人惊讶。考虑到所有因素,这并不一定是一个坏主意,但它确实是不利的。
我想我只在以下情况下使用引用作为成员:
例如,注入的依赖项(例如工厂或服务对象)可能是合适的。与此相反,在 C++ 中,您通常更喜欢使用模板参数而不是对象注入依赖项,因此可能不会出现问题。
我还发现,使用 C++ 的时间越长,我就越希望类型可分配,除非有充分的理由不这样做。以您想要的方式减少编译时依赖性的常用技巧是“Pimpl”,而不是“Rimpl”,这是有原因的。通过从对象成员切换到引用成员,您可以使类成为非默认可复制的,而以前它可能是可复制的。此实现细节不应限制类的接口。使用 Pimpl,您可以干净地实现分配和交换。有了这些参考资料,您就必须分配或交换这两个成员。如果第二次交换失败,您就失去了强大的异常保证:尽管如果您的 CMemberX 和 CMemberY 类具有无失败分配和交换,这并不重要。
所以我认为我不喜欢这种情况下的参考,但我还没有看到你的其余代码。也许有一些原因导致没有任何关于赋值的担忧 - 例如,如果 CContainer 本身是一个 RAII 类,那么通常它应该支持的唯一生命周期操作是构造和销毁。
I think it's a little surprising to use a reference when there are ownership semantics. Doesn't necessarily make it a bad idea, all things considered, but it does weigh against.
I think I've only ever used references as members in cases where both:
So for example, injected dependencies such as factory or service objects could be suitable. As against that, in C++ you'd often prefer to inject dependencies with template parameters rather than objects, so the issue might not arise.
I also find that the longer I use C++, the more I want types to be Assignable unless there's a really good reason not to be. The usual trick for reducing compile-time dependencies in the way you want is "Pimpl", not "Rimpl", for a reason. By switching from an object member to a reference member, you are making your class non-default-copyable, where previously perhaps it was copyable. This implementation detail shouldn't constrain the class's interface. With Pimpl you can cleanly implement assignment and swap. With these references you would have to assign or swap both members. If the second swap fails, you've lost the strong exception guarantee: although if your CMemberX and CMemberY classes have no-fail assignment and swap, this doesn't matter.
So I don't think I like the reference in this case, but then I haven't seen the rest of your code. Maybe there's some reason why none of the concerns about assignment apply - for instance if CContainer is itself a RAII class, then usually the only lifecycle operations it should support are construction and destruction.
这里有很多关于使用引用作为成员的可取性的问题(例如 我应该更喜欢成员数据中的指针还是引用?),在我看来,大多数人的意见(也恰好是我的)是 - 不。如果您不想更改指针,请将它们设置为常量 - 根据您的代码,我看不出它们如何可能为 NULL。
There have been a lot of questions here about the desirability of using references as members (for example Should I prefer pointers or references in member data?), and it seems to me that the majority opinion (which also happens to be mine) is - don't. If you don't want the pointers to be changed make them const - I can't see how, given your code, they can possibly be NULL.
动态分配对于您想要的东西没有任何帮助:不必重新编译 CClient 和 CContainer。
使用前向声明时唯一允许的用途是声明指向前向声明类型的指针。
一旦您使用前向声明类型的方法或成员,它就不会编译:编译器必须知道它正在使用的任何内容的完整类型。
简而言之:要么你永远不必重新编译[你只是声明指向前向声明类型的指针],要么你总是必须重新编译,以防你确实使用 CContainer。
Dynamic allocation does nothing for you in respect of what you want: not having to recompile CClient and CContainer.
The only allowed use when using forward declarations is declaring pointer(s) to the forward-declared type.
As soon as you use a method or a member of the forward-declared type it will not compile: the compiler MUST know the full type of whatever it is using.
In short: either you never have to recompile [you are only declaring pointers to the forward declared type] OR you always have to recompile, in case you actually DO use CContainer.
Steve Jessop 已经顺便提到了 pImpl 习惯用法,但我认为如果您还没有遇到过它,您应该检查一下:编译防火墙
Steve Jessop already mentioned the pImpl idiom in passing, but I think you should check this out if you haven't already come across it: Compilation Firewalls
在问题的第二个代码块中,您有私有成员指针,它们与父类一起初始化和销毁。这些信息应该足以让代码的读者了解发生了什么。
此外,您可以将指针声明为 const:
CMember* const m_pX;
以指示它们在初始化后无法更改。现在编译器将捕获意外的更改。In the second block of code in your question you have private member pointers which are initialised and destroyed along with the parent class. This information should be enough to the reader of your code to realise what is going on.
In addition you could declare the pointers const:
CMember* const m_pX;
to indicate that they cannot be changed after initialisation. Now the compiler will catch accidental changes.你并没有真正给自己买任何东西。
(在有限情况下编译时间稍短)。
但是您正在堆积大量需要维护的其他代码。
如果这些对象是自然成员,则将它们保留为成员。
通过在堆栈上创建它们并将它们存储为指针或引用,您必须提出一大堆需要代码来回答的棘手问题。
使用指针或引用,您将必须做额外的工作来复制已提供的此功能。
但您仍然存在期望成员被复制的问题。
否则,您需要开始考虑共享指针来实现您需要的功能。
就目前情况而言,版本 2 和 3(问题中的代码)存在严重缺陷,唯一真正有效的版本是 1。
在我看来,一个简单的事实是版本 1 的维护成本会低得多,因此建议使用以下任一版本版本 2 或 3 会适得其反。与添加到代码中的复杂性相比,更改成员时再编译一个类所需的额外时间相对较小。
您还在其他人的评论中提到代码并不像上面描述的那么干净。这只是强调了我的观点,即这是一个糟糕的优化,它将使得类很难正常工作并保持在该状态。
You are not really buying yourself anything.
(Slightly shorter compile time in limited situations).
But you are heaping a whole lot of other code that needs to be maintained onto your plate.
If these objects are natural members then leave them as members.
By creating them on the stack and storing them as pointers or references you have to ask a whole bunch of sticky questions that need code to answer them.
Using pointers or references you are going to have to do extra work to replicate this functionality that is already provided.
But you still have the problem with expecting the members to be copied.
Otherwise you need to start thinking about shared pointers to implement the functionality you need.
As it stands versions 2 and 3 (of the code in the question) are seriously flawed and the only version that actually works is 1.
In my mind the simple fact that maintenance costs will be so much lower with version 1, that recommending either of version 2 or 3 is counter productive. The extra time to compile one more class when a member is changed is relatively small in comparison to the complexity you are adding to the code.
Also you mention in somebody else s comment that the code is not quite as clean as that described above. This just emphasizes my point that this is bad optimization that will make it hard to get the class working correctly and keep it maintained in that state.