4

我最近遇到了一些看起来像这样的 C++ 代码:

class SomeObject
{
private:
    // NOT a pointer
    BigObject foobar;

public:
    BigObject * getFoobar() const
    {
        return &foobar;
    }
};

我问程序员为什么他不只是让 foobar 成为指针,他说这样他就不必担心分配/释放内存。我问他是否考虑使用一些智能指针,他说这同样有效。

这是不好的做法吗?这似乎很hackish。

4

6 回答 6

6

That's perfectly reasonable, and not "hackish" in any way; although it might be considered better to return a reference to indicate that the object definitely exists. A pointer might be null, and might lead some to think that they should delete it after use.

The object has to exist somewhere, and existing as a member of an object is usually as good as existing anywhere else. Adding an extra level of indirection by dynamically allocating it separately from the object that owns it makes the code less efficient, and adds the burden of making sure it's correctly deallocated.

Of course, the member function can't be const if it returns a non-const reference or pointer to a member. That's another advantage of making it a member: a const qualifier on SomeObject applies to its members too, but doesn't apply to any objects it merely has a pointer to.

The only danger is that the object might be destroyed while someone still has a pointer or reference to it; but that danger is still present however you manage it. Smart pointers can help here, if the object lifetimes are too complex to manage otherwise.

于 2012-09-07T10:28:22.990 回答
3

您正在返回指向成员变量而不是引用的指针。这是一个糟糕的设计。您的类管理foobar对象的生命周期,并通过返回指向其成员的指针,您可以使类的使用者在SomeObject对象的生命周期之外继续使用指针。它还使用户能够根据需要更改SomeObject对象的状态。

相反,您应该重构您的类以包含将在 SomeObject 类中的 foobar 上执行的操作作为方法。

附言。考虑正确命名你的类。当你定义它是一个类。当您实例化时,您将拥有该类的一个对象。

于 2012-09-07T10:32:40.907 回答
2

It's generally considered less than ideal to return pointers to internal data at all; it prevents the class from managing access to its own data. But if you want to do that anyway I see no great problem here; it simplifies the management of memory.

于 2012-09-07T10:26:04.533 回答
1

As long as the caller knows that the pointer returned from getFoobar() becomes invalid when the SomeObject object destructs, it's fine. Such provisos and caveats are common in older C++ programs and frameworks.

Even current libraries have to do this for historical reasons. e.g. std::string::c_str, which returns a pointer to an internal buffer in the string, which becomes unusable when the string destructs.

Of course, that is difficult to ensure in a large or complex program. In modern C++ the preferred approach is to give everything simple "value semantics" as far as possible, so that every object's life time is controlled by the code that uses it in a trivial way. So there are no naked pointers, no explicit new or delete calls scattered around your code, etc., and so no need to require programmers to manually ensure they are following the rules.

(And then you can resort to smart pointers in cases where you are totally unable to avoid shared responsibility for object lifetimes.)

于 2012-09-07T10:26:16.633 回答
1

Is this bad practice? It seems very hackish.

It is. If the class goes out of scope before the pointer does, the member variable will no longer exist, yet a pointer to it still exists. Any attempt to dereference that pointer post class destruction will result in undefined behaviour - this could result in a crash, or it could result in hard to find bugs where arbitrary memory is read and treated as a BigObject.

if he considered using some smart pointer

Using smart pointers, specifically std::shared_ptr<T> or the boost version, would technically work here and avoid the potential crash (if you allocate via the shared pointer constructor) - however, it also confuses who owns that pointer - the class, or the caller? Furthermore, I'm not sure you can just add a pointer to an object to a smart pointer.

Both of these two points deal with the technical issue of getting a pointer out of a class, but the real question should be "why?" as in "why are you returning a pointer from a class?" There are cases where this is the only way, but more often than not you don't need to return a pointer. For example, suppose that variable needs to be passed to a C API which takes a pointer to that type. In this case, you would probably be better encapsulating that C call in the class.

于 2012-09-07T10:28:49.243 回答
0

这里有两个不相关的问题:

1)您希望您的实例如何SomeObject管理BigObject它需要的实例?如果每个实例都SomeObject需要自己的BigObject,那么BigObject数据成员是完全合理的。在某些情况下,您想做一些不同的事情,但除非出现这种情况,否则请坚持使用简单的解决方案。

2)你想让用户SomeObject直接访问它BigObject吗?默认情况下,在良好封装的基础上,这里的答案是“否”。但是,如果您确实愿意,那么这不会改变对 (1) 的评估。此外,如果您确实愿意,您不一定需要通过指针来这样做——它可以是通过引用甚至是公共数据成员。

第三个可能的问题可能会改变对 (1) 的评估:

3) 您是否要让用户SomeObject直接访问BigObject他们在获得实例的生命周期之后继续使用的实例SomeObject?如果是这样,那么数据成员当然不好。正确的解决方案可能是shared_ptr, 或 for是一个每次调用时都SomeObject::getFooBar返回不同的工厂。BigObject

总之:

  • 除了它不能编译(getFooBar()需要返回const BigObject*)之外,到目前为止没有理由认为这段代码是错误的。可能会出现其他问题,使其出错。
  • 返回const &而不是返回可能是更好的风格const *foobar您返回的内容与是否应该是BigObject数据成员无关。
  • 制作指针或智能指针当然没有“只是” foobar——任何一个都需要额外的代码来创建BigObject指向的实例。
于 2012-09-07T11:19:33.957 回答